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 23df02586..74cd7ea9a 100644 --- a/src/main/java/io/github/pylonmc/pylon/base/BaseBlocks.java +++ b/src/main/java/io/github/pylonmc/pylon/base/BaseBlocks.java @@ -107,7 +107,7 @@ public static void initialize() { PylonBlock.register(BaseKeys.HYDRAULIC_HAMMER_HEAD, Material.STONE_BRICKS, HydraulicHammerHead.class); PylonBlock.register(BaseKeys.HYDRAULIC_PIPE_BENDER, Material.WAXED_CHISELED_COPPER, HydraulicPipeBender.class); PylonBlock.register(BaseKeys.HYDRAULIC_TABLE_SAW, Material.WAXED_CUT_COPPER, HydraulicTableSaw.class); - PylonBlock.register(BaseKeys.SOLAR_LENS, Material.GLASS_PANE, PylonBlock.class); + PylonBlock.register(BaseKeys.SOLAR_LENS, Material.GLASS_PANE, SolarLens.class); PylonBlock.register(BaseKeys.PURIFICATION_TOWER_GLASS, Material.LIGHT_GRAY_STAINED_GLASS, PylonBlock.class); PylonBlock.register(BaseKeys.PURIFICATION_TOWER_CAP, Material.QUARTZ_SLAB, PylonBlock.class); PylonBlock.register(BaseKeys.SOLAR_PURIFICATION_TOWER_1, Material.WAXED_COPPER_BLOCK, SolarPurificationTower.class); @@ -155,5 +155,10 @@ public static void initialize() { 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); + PylonBlock.register(BaseKeys.CARGO_MONITOR, Material.STRUCTURE_VOID, CargoMonitor.class); + PylonBlock.register(BaseKeys.CARGO_METER, Material.STRUCTURE_VOID, CargoMeter.class); + PylonBlock.register(BaseKeys.CARGO_GATE, Material.STRUCTURE_VOID, CargoGate.class); + PylonBlock.register(BaseKeys.CARGO_ACCUMULATOR, Material.STRUCTURE_VOID, CargoAccumulator.class); + PylonBlock.register(BaseKeys.CARGO_FLUID_ACCUMULATOR, Material.STRUCTURE_VOID, CargoFluidAccumulator.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 5176a8044..dae51af27 100644 --- a/src/main/java/io/github/pylonmc/pylon/base/BaseItems.java +++ b/src/main/java/io/github/pylonmc/pylon/base/BaseItems.java @@ -1236,10 +1236,10 @@ private BaseItems() { } public static final ItemStack FLUID_METER = ItemStackBuilder.pylon(Material.STRUCTURE_VOID, BaseKeys.FLUID_METER) - .set(DataComponentTypes.ITEM_MODEL, Material.WHITE_CONCRETE.getKey()) + .set(DataComponentTypes.ITEM_MODEL, Material.LIGHT_BLUE_STAINED_GLASS.getKey()) .build(); static { - PylonItem.register(FluidFilter.Item.class, FLUID_METER, BaseKeys.FLUID_METER); + PylonItem.register(FluidMeter.Item.class, FLUID_METER, BaseKeys.FLUID_METER); BasePages.FLUID_MACHINES.addItem(FLUID_METER); } @@ -2020,7 +2020,7 @@ private BaseItems() { } public static final ItemStack CARGO_SPLITTER = ItemStackBuilder.pylon(Material.STRUCTURE_VOID, BaseKeys.CARGO_SPLITTER) - .set(DataComponentTypes.ITEM_MODEL, Material.STRIPPED_CRIMSON_HYPHAE.getKey()) + .set(DataComponentTypes.ITEM_MODEL, Material.STRIPPED_CRIMSON_STEM.getKey()) .build(); static { PylonItem.register(CargoSplitter.Item.class, CARGO_SPLITTER, BaseKeys.CARGO_SPLITTER); @@ -2028,7 +2028,7 @@ private BaseItems() { } public static final ItemStack CARGO_MERGER = ItemStackBuilder.pylon(Material.STRUCTURE_VOID, BaseKeys.CARGO_MERGER) - .set(DataComponentTypes.ITEM_MODEL, Material.STRIPPED_WARPED_HYPHAE.getKey()) + .set(DataComponentTypes.ITEM_MODEL, Material.STRIPPED_WARPED_STEM.getKey()) .build(); static { PylonItem.register(CargoSplitter.Item.class, CARGO_MERGER, BaseKeys.CARGO_MERGER); @@ -2044,13 +2044,53 @@ private BaseItems() { } public static final ItemStack CARGO_FILTER = ItemStackBuilder.pylon(Material.STRUCTURE_VOID, BaseKeys.CARGO_FILTER) - .set(DataComponentTypes.ITEM_MODEL, Material.PINK_TERRACOTTA.getKey()) + .set(DataComponentTypes.ITEM_MODEL, Material.COMPARATOR.getKey()) .build(); static { PylonItem.register(CargoValve.Item.class, CARGO_FILTER, BaseKeys.CARGO_FILTER); BasePages.CARGO.addItem(CARGO_FILTER); } + public static final ItemStack CARGO_MONITOR = ItemStackBuilder.pylon(Material.STRUCTURE_VOID, BaseKeys.CARGO_MONITOR) + .set(DataComponentTypes.ITEM_MODEL, Material.PINK_STAINED_GLASS.getKey()) + .build(); + static { + PylonItem.register(CargoMonitor.Item.class, CARGO_MONITOR, BaseKeys.CARGO_MONITOR); + BasePages.CARGO.addItem(CARGO_MONITOR); + } + + public static final ItemStack CARGO_METER = ItemStackBuilder.pylon(Material.STRUCTURE_VOID, BaseKeys.CARGO_METER) + .set(DataComponentTypes.ITEM_MODEL, Material.LIGHT_BLUE_STAINED_GLASS.getKey()) + .build(); + static { + PylonItem.register(CargoMeter.Item.class, CARGO_METER, BaseKeys.CARGO_METER); + BasePages.CARGO.addItem(CARGO_METER); + } + + public static final ItemStack CARGO_GATE = ItemStackBuilder.pylon(Material.STRUCTURE_VOID, BaseKeys.CARGO_GATE) + .set(DataComponentTypes.ITEM_MODEL, Material.REPEATER.getKey()) + .build(); + static { + PylonItem.register(CargoGate.Item.class, CARGO_GATE, BaseKeys.CARGO_GATE); + BasePages.CARGO.addItem(CARGO_GATE); + } + + public static final ItemStack CARGO_ACCUMULATOR = ItemStackBuilder.pylon(Material.STRUCTURE_VOID, BaseKeys.CARGO_ACCUMULATOR) + .set(DataComponentTypes.ITEM_MODEL, Material.REDSTONE_LAMP.getKey()) + .build(); + static { + PylonItem.register(CargoAccumulator.Item.class, CARGO_ACCUMULATOR, BaseKeys.CARGO_ACCUMULATOR); + BasePages.CARGO.addItem(CARGO_ACCUMULATOR); + } + + public static final ItemStack CARGO_FLUID_ACCUMULATOR = ItemStackBuilder.pylon(Material.STRUCTURE_VOID, BaseKeys.CARGO_FLUID_ACCUMULATOR) + .set(DataComponentTypes.ITEM_MODEL, Material.NOTE_BLOCK.getKey()) + .build(); + static { + PylonItem.register(CargoFluidAccumulator.Item.class, CARGO_FLUID_ACCUMULATOR, BaseKeys.CARGO_FLUID_ACCUMULATOR); + BasePages.CARGO.addItem(CARGO_FLUID_ACCUMULATOR); + } + 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 5ed323dfb..5367eb12d 100644 --- a/src/main/java/io/github/pylonmc/pylon/base/BaseKeys.java +++ b/src/main/java/io/github/pylonmc/pylon/base/BaseKeys.java @@ -326,6 +326,11 @@ public class BaseKeys { 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 CARGO_MONITOR = baseKey("cargo_monitor"); + public static final NamespacedKey CARGO_METER = baseKey("cargo_meter"); + public static final NamespacedKey CARGO_GATE = baseKey("cargo_gate"); + public static final NamespacedKey CARGO_ACCUMULATOR = baseKey("cargo_accumulator"); + public static final NamespacedKey CARGO_FLUID_ACCUMULATOR = baseKey("cargo_fluid_accumulator"); 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/BasePages.java b/src/main/java/io/github/pylonmc/pylon/base/BasePages.java index 95bf9008e..edb31a0d8 100644 --- a/src/main/java/io/github/pylonmc/pylon/base/BasePages.java +++ b/src/main/java/io/github/pylonmc/pylon/base/BasePages.java @@ -21,7 +21,7 @@ public class BasePages { public static final SimpleStaticGuidePage SIMPLE_MACHINES = new SimpleStaticGuidePage(baseKey("simple_machines"), Material.SMOOTH_STONE_SLAB); public static final SimpleStaticGuidePage SMELTING = new SimpleStaticGuidePage(baseKey("smelting"), Material.DEEPSLATE_TILES); public static final SimpleStaticGuidePage FLUID_PIPES_AND_TANKS = new SimpleStaticGuidePage(baseKey("fluid_pipes_and_tanks"), Material.ORANGE_TERRACOTTA); - public static final SimpleStaticGuidePage FLUID_MACHINES = new SimpleStaticGuidePage(baseKey("fluid_machines"), Material.CAULDRON); + public static final SimpleStaticGuidePage FLUID_MACHINES = new SimpleStaticGuidePage(baseKey("fluid_machines"), Material.LIGHT_BLUE_STAINED_GLASS); public static final SimpleStaticGuidePage HYDRAULICS = new SimpleStaticGuidePage(baseKey("hydraulics"), Material.BLUE_CONCRETE_POWDER); public static final SimpleStaticGuidePage CARGO = new SimpleStaticGuidePage(baseKey("cargo"), Material.HOPPER); public static final SimpleStaticGuidePage DIESEL_MACHINES = new SimpleStaticGuidePage(baseKey("diesel_machines"), Material.YELLOW_CONCRETE); diff --git a/src/main/java/io/github/pylonmc/pylon/base/content/machines/cargo/CargoAccumulator.java b/src/main/java/io/github/pylonmc/pylon/base/content/machines/cargo/CargoAccumulator.java new file mode 100644 index 000000000..9273e6979 --- /dev/null +++ b/src/main/java/io/github/pylonmc/pylon/base/content/machines/cargo/CargoAccumulator.java @@ -0,0 +1,267 @@ +package io.github.pylonmc.pylon.base.content.machines.cargo; + +import com.google.common.base.Preconditions; +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.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.BlockDisplayBuilder; +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.LogisticGroupType; +import io.github.pylonmc.pylon.core.util.MachineUpdateReason; +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.entity.BlockDisplay; +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.Arrays; +import java.util.List; + +import static io.github.pylonmc.pylon.base.util.BaseUtils.baseKey; + + +public class CargoAccumulator extends PylonBlock implements + PylonDirectionalBlock, + PylonGuiBlock, + PylonCargoBlock { + + public static final NamespacedKey THRESHOLD_KEY = baseKey("threshold"); + + private final VirtualInventory inputInventory = new VirtualInventory(5); + private final VirtualInventory outputInventory = new VirtualInventory(5); + + public final int transferRate = getSettings().getOrThrow("transfer-rate", ConfigAdapter.INT); + + public int threshold; + + public final ItemStackBuilder mainStack = ItemStackBuilder.of(Material.LIGHT_GRAY_CONCRETE) + .addCustomModelDataString(getKey() + ":main"); + 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 final ItemStackBuilder thresholdButtonStack = ItemStackBuilder.gui(Material.WHITE_CONCRETE, getKey() + "threshold_button") + .lore(Component.translatable("pylon.pylonbase.gui.threshold_button.lore")); + + 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 CargoAccumulator(@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.6) + ) + .build(block.getLocation().toCenterLocation()) + ); + + addEntity("side1", new BlockDisplayBuilder() + .blockData(Material.REDSTONE_LAMP.createBlockData("[lit=false]")) + .transformation(new TransformBuilder() + .lookAlong(getFacing()) + .rotate(Math.PI / 2, Math.PI / 2, 0) + .scale(0.45, 0.45, 0.65) + ) + .build(block.getLocation().toCenterLocation()) + ); + + addEntity("side2", new BlockDisplayBuilder() + .blockData(Material.REDSTONE_LAMP.createBlockData("[lit=false]")) + .transformation(new TransformBuilder() + .lookAlong(getFacing()) + .rotate(Math.PI / 2, Math.PI / 2, 0) + .scale(0.65, 0.45, 0.45) + ) + .build(block.getLocation().toCenterLocation()) + ); + + addEntity("input", new ItemDisplayBuilder() + .itemStack(inputStack) + .transformation(new TransformBuilder() + .lookAlong(getFacing()) + .translate(0, 0, 0.125) + .scale(0.4, 0.4, 0.4) + ) + .build(block.getLocation().toCenterLocation()) + ); + + addEntity("output", new ItemDisplayBuilder() + .itemStack(outputStack) + .transformation(new TransformBuilder() + .lookAlong(getFacing()) + .translate(0, 0, -0.125) + .scale(0.4, 0.4, 0.4) + ) + .build(block.getLocation().toCenterLocation()) + ); + + threshold = 1; + } + + @SuppressWarnings("unused") + public CargoAccumulator(@NotNull Block block, @NotNull PersistentDataContainer pdc) { + super(block, pdc); + threshold = pdc.get(THRESHOLD_KEY, PylonSerializers.INTEGER); + } + + @Override + public void write(@NotNull PersistentDataContainer pdc) { + pdc.set(THRESHOLD_KEY, PylonSerializers.INTEGER, threshold); + } + + @Override + public @NotNull Gui createGui() { + return Gui.normal() + .setStructure( + "# I i i i i i I #", + "# # # # # # # # #", + "# O o o o o o O #", + "# # # # # # # # #", + "# # # # t # # # #" + ) + .addIngredient('#', GuiItems.background()) + .addIngredient('i', inputInventory) + .addIngredient('I', GuiItems.input()) + .addIngredient('o', outputInventory) + .addIngredient('O', GuiItems.output()) + .addIngredient('t', new ThresholdButton()) + .build(); + } + + @Override + public void postInitialise() { + createLogisticGroup("input", LogisticGroupType.INPUT, inputInventory); + createLogisticGroup("output", LogisticGroupType.OUTPUT, outputInventory); + inputInventory.setPostUpdateHandler(event -> { + if (!(event.getUpdateReason() instanceof MachineUpdateReason)) { + doTransfer(); + } + }); + outputInventory.setPostUpdateHandler(event -> { + if (!(event.getUpdateReason() instanceof MachineUpdateReason)) { + doTransfer(); + } + }); + } + + @Override + public @Nullable WailaDisplay getWaila(@NotNull Player player) { + return new WailaDisplay(getDefaultWailaTranslationKey().arguments( + PylonArgument.of("threshold", threshold) + )); + } + + private void doTransfer() { + int inputTotal = 0; + for (ItemStack stack : inputInventory.getItems()) { + if (stack != null) { + inputTotal += stack.getAmount(); + } + } + + int outputTotal = 0; + for (ItemStack stack : outputInventory.getItems()) { + if (stack != null) { + outputTotal += stack.getAmount(); + } + } + + if (outputTotal == 0) { + getLogisticGroupOrThrow("input").setFilter(null); + getHeldEntityOrThrow(BlockDisplay.class, "side1") + .setBlock(Material.REDSTONE_LAMP.createBlockData("[lit=false]")); + getHeldEntityOrThrow(BlockDisplay.class, "side2") + .setBlock(Material.REDSTONE_LAMP.createBlockData("[lit=false]")); + } + + if (inputTotal >= threshold) { + List stacks = Arrays.stream(inputInventory.getItems()).toList(); + Preconditions.checkState(outputInventory.canHold(stacks)); + for (ItemStack stack : stacks) { + outputInventory.addItem(new MachineUpdateReason(), stack); + } + for (int slot = 0; slot < inputInventory.getItems().length; slot++) { + inputInventory.setItem(new MachineUpdateReason(), slot, null); + } + getLogisticGroupOrThrow("input").setFilter(stack -> false); + getHeldEntityOrThrow(BlockDisplay.class, "side1") + .setBlock(Material.REDSTONE_LAMP.createBlockData("[lit=true]")); + getHeldEntityOrThrow(BlockDisplay.class, "side2") + .setBlock(Material.REDSTONE_LAMP.createBlockData("[lit=true]")); + } + } + + public class ThresholdButton extends AbstractItem { + + @Override + public ItemProvider getItemProvider() { + return thresholdButtonStack + .name((Component.translatable("pylon.pylonbase.gui.threshold_button.name").arguments( + PylonArgument.of("threshold", threshold) + ))); + } + + @Override + public void handleClick( + @NotNull ClickType clickType, + @NotNull Player player, + @NotNull InventoryClickEvent event + ) { + if (clickType.isLeftClick()) { + threshold += 1; + } else { + threshold = Math.max(1, threshold - 1); + } + notifyWindows(); + doTransfer(); + } + } +} diff --git a/src/main/java/io/github/pylonmc/pylon/base/content/machines/cargo/CargoBuffer.java b/src/main/java/io/github/pylonmc/pylon/base/content/machines/cargo/CargoBuffer.java index 14c7fc6f2..4a6078401 100644 --- a/src/main/java/io/github/pylonmc/pylon/base/content/machines/cargo/CargoBuffer.java +++ b/src/main/java/io/github/pylonmc/pylon/base/content/machines/cargo/CargoBuffer.java @@ -80,7 +80,7 @@ public CargoBuffer(@NotNull Block block, @NotNull BlockCreateContext context) { .itemStack(mainStack) .transformation(new TransformBuilder() .lookAlong(getFacing()) - .scale(0.65) + .scale(0.6) ) .build(block.getLocation().toCenterLocation()) ); @@ -90,7 +90,7 @@ public CargoBuffer(@NotNull Block block, @NotNull BlockCreateContext context) { .transformation(new TransformBuilder() .lookAlong(getFacing()) .rotate(Math.PI / 2, Math.PI / 2, 0) - .scale(0.5, 0.5, 0.7) + .scale(0.45, 0.45, 0.65) ) .build(block.getLocation().toCenterLocation()) ); @@ -100,7 +100,7 @@ public CargoBuffer(@NotNull Block block, @NotNull BlockCreateContext context) { .transformation(new TransformBuilder() .lookAlong(getFacing()) .rotate(Math.PI / 2, Math.PI / 2, 0) - .scale(0.7, 0.5, 0.5) + .scale(0.65, 0.45, 0.45) ) .build(block.getLocation().toCenterLocation()) ); @@ -109,7 +109,7 @@ public CargoBuffer(@NotNull Block block, @NotNull BlockCreateContext context) { .itemStack(inputStack) .transformation(new TransformBuilder() .lookAlong(getFacing()) - .translate(0, 0, 0.15) + .translate(0, 0, 0.125) .scale(0.4, 0.4, 0.4) ) .build(block.getLocation().toCenterLocation()) @@ -119,7 +119,7 @@ public CargoBuffer(@NotNull Block block, @NotNull BlockCreateContext context) { .itemStack(outputStack) .transformation(new TransformBuilder() .lookAlong(getFacing()) - .translate(0, 0, -0.15) + .translate(0, 0, -0.125) .scale(0.4, 0.4, 0.4) ) .build(block.getLocation().toCenterLocation()) 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 1d839ad5a..7615a6b05 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 @@ -196,7 +196,7 @@ public void onDuctConnected(@NotNull PylonCargoConnectEvent event) { public void onDuctDisconnected(@NotNull PylonCargoDisconnectEvent event) { // Allow connecting to all faces now that there are zero connections List faces = PylonUtils.perpendicularImmediateFaces(getFacing()); - faces.add(getFacing().getOppositeFace()); + faces.add(getFacing()); for (BlockFace face : faces) { addCargoLogisticGroup(face, "output"); } 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 index 2bfc7ca54..e35017492 100644 --- 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 @@ -7,6 +7,7 @@ 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.BlockDisplayBuilder; 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; @@ -22,6 +23,7 @@ import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; +import org.bukkit.entity.BlockDisplay; import org.bukkit.inventory.ItemStack; import org.bukkit.persistence.PersistentDataContainer; import org.jetbrains.annotations.NotNull; @@ -33,14 +35,16 @@ import java.util.Map; -public class CargoFilter extends PylonBlock - implements PylonDirectionalBlock, PylonGuiBlock, PylonCargoBlock{ +public class CargoFilter extends PylonBlock implements + PylonDirectionalBlock, + PylonGuiBlock, + PylonCargoBlock { 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 leftInventory = new VirtualInventory(1); + private final VirtualInventory rightInventory = new VirtualInventory(1); private final VirtualInventory filterInventory = new VirtualInventory(5); public final ItemStackBuilder mainStack = ItemStackBuilder.of(Material.LIGHT_GRAY_CONCRETE) @@ -49,24 +53,25 @@ public class CargoFilter extends PylonBlock .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 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 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 final ItemStackBuilder leftGuiStack = ItemStackBuilder.gui(Material.YELLOW_STAINED_GLASS_PANE, getKey() + "left") + .name(Component.translatable("pylon.pylonbase.gui.left")); + public final ItemStackBuilder rightGuiStack = ItemStackBuilder.gui(Material.LIGHT_BLUE_STAINED_GLASS_PANE, getKey() + "right") + .name(Component.translatable("pylon.pylonbase.gui.right")); + @Override public @NotNull Map<@NotNull String, @NotNull Inventory> createInventoryMapping() { return Map.of( "input", inputInventory, - "filtered", filteredInventory, - "unfiltered", unfilteredInventory, + "left", leftInventory, + "right", rightInventory, "filter", filterInventory ); } @@ -97,35 +102,25 @@ public CargoFilter(@NotNull Block block, @NotNull BlockCreateContext context) { setFacing(context.getFacing()); addCargoLogisticGroup(getFacing(), "input"); - addCargoLogisticGroup(PylonUtils.rotateFaceToReference(getFacing(), BlockFace.EAST), "filtered"); - addCargoLogisticGroup(PylonUtils.rotateFaceToReference(getFacing(), BlockFace.WEST), "unfiltered"); + 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("vertical1", new ItemDisplayBuilder() - .itemStack(verticalStack) - .transformation(new TransformBuilder() - .lookAlong(getFacing()) - .translate(-0.15, 0, 0) - .scale(0.15, 0.7, 0.5) + .scale(0.7) ) .build(block.getLocation().toCenterLocation()) ); - addEntity("vertical2", new ItemDisplayBuilder() - .itemStack(verticalStack) + addEntity("comparator", new BlockDisplayBuilder() + .blockData(Material.COMPARATOR.createBlockData("[powered=false]")) .transformation(new TransformBuilder() .lookAlong(getFacing()) - .translate(0.15, 0, 0) - .scale(0.15, 0.7, 0.5) + .translate(0, 0.6, 0) + .scale(0.5) ) .build(block.getLocation().toCenterLocation()) ); @@ -134,27 +129,27 @@ public CargoFilter(@NotNull Block block, @NotNull BlockCreateContext context) { .itemStack(inputStack) .transformation(new TransformBuilder() .lookAlong(getFacing()) - .translate(0, 0, 0.15) + .translate(0, 0, 0.2) .scale(0.4, 0.4, 0.4) ) .build(block.getLocation().toCenterLocation()) ); - addEntity("output_filtered", new ItemDisplayBuilder() - .itemStack(filteredStack) + addEntity("output_left", new ItemDisplayBuilder() + .itemStack(outputLeftStack) .transformation(new TransformBuilder() .lookAlong(getFacing()) - .translate(-0.15, 0, 0) + .translate(-0.2, 0, 0) .scale(0.4, 0.4, 0.4) ) .build(block.getLocation().toCenterLocation()) ); - addEntity("output_unfiltered", new ItemDisplayBuilder() - .itemStack(unfilteredStack) + addEntity("output_right", new ItemDisplayBuilder() + .itemStack(outputRightStack) .transformation(new TransformBuilder() .lookAlong(getFacing()) - .translate(0.15, 0, 0) + .translate(0.2, 0, 0) .scale(0.4, 0.4, 0.4) ) .build(block.getLocation().toCenterLocation()) @@ -178,12 +173,12 @@ public CargoFilter(@NotNull Block block, @NotNull PersistentDataContainer pdc) { "# # # # # # # # #" ) .addIngredient('#', GuiItems.background()) - .addIngredient('L', filteredGuiStack) - .addIngredient('l', filteredInventory) + .addIngredient('L', leftGuiStack) + .addIngredient('l', leftInventory) .addIngredient('I', GuiItems.input()) .addIngredient('i', inputInventory) - .addIngredient('R', unfilteredGuiStack) - .addIngredient('r', unfilteredInventory) + .addIngredient('R', rightGuiStack) + .addIngredient('r', rightInventory) .addIngredient('f', filterInventory) .addIngredient('F', filterGuiStack) .build(); @@ -192,19 +187,19 @@ public CargoFilter(@NotNull Block block, @NotNull PersistentDataContainer pdc) { @Override public void postInitialise() { createLogisticGroup("input", LogisticGroupType.INPUT, new VirtualInventoryLogisticSlot(inputInventory, 0)); - createLogisticGroup("filtered", LogisticGroupType.OUTPUT, new VirtualInventoryLogisticSlot(filteredInventory, 0)); - createLogisticGroup("unfiltered", LogisticGroupType.OUTPUT, new VirtualInventoryLogisticSlot(unfilteredInventory, 0)); + createLogisticGroup("left", LogisticGroupType.OUTPUT, new VirtualInventoryLogisticSlot(leftInventory, 0)); + createLogisticGroup("right", LogisticGroupType.OUTPUT, new VirtualInventoryLogisticSlot(rightInventory, 0)); inputInventory.setPostUpdateHandler(event -> { if (!(event.getUpdateReason() instanceof MachineUpdateReason)) { doSplit(); } }); - filteredInventory.setPostUpdateHandler(event -> { + leftInventory.setPostUpdateHandler(event -> { if (!(event.getUpdateReason() instanceof MachineUpdateReason)) { doSplit(); } }); - unfilteredInventory.setPostUpdateHandler(event -> { + rightInventory.setPostUpdateHandler(event -> { if (!(event.getUpdateReason() instanceof MachineUpdateReason)) { doSplit(); } @@ -230,33 +225,36 @@ private void doSplit() { } } + getHeldEntityOrThrow(BlockDisplay.class, "comparator") + .setBlock(Material.COMPARATOR.createBlockData("[powered=" + (matchesFilter ? "true" : "false") +"]")); + if (matchesFilter) { - ItemStack filteredStack = filteredInventory.getItem(0); + ItemStack filteredStack = leftInventory.getItem(0); if (filteredStack == null || (filteredStack.isSimilar(input) && filteredStack.getAmount() < filteredStack.getMaxStackSize()) ) { if (filteredStack == null) { - filteredInventory.setItem(new MachineUpdateReason(), 0, input); + leftInventory.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)); + leftInventory.setItem(new MachineUpdateReason(), 0, filteredStack.asQuantity(newAmount)); inputInventory.setItem(new MachineUpdateReason(), 0, input.subtract(toSubtract)); } } } else { - ItemStack unfilteredStack = unfilteredInventory.getItem(0); + ItemStack unfilteredStack = rightInventory.getItem(0); if (unfilteredStack == null || (unfilteredStack.isSimilar(input) && unfilteredStack.getAmount() < unfilteredStack.getMaxStackSize()) ) { if (unfilteredStack == null) { - unfilteredInventory.setItem(new MachineUpdateReason(), 0, input); + rightInventory.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)); + rightInventory.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/CargoFluidAccumulator.java b/src/main/java/io/github/pylonmc/pylon/base/content/machines/cargo/CargoFluidAccumulator.java new file mode 100644 index 000000000..60ae5cfb5 --- /dev/null +++ b/src/main/java/io/github/pylonmc/pylon/base/content/machines/cargo/CargoFluidAccumulator.java @@ -0,0 +1,358 @@ +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.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.PylonFluidTank; +import io.github.pylonmc.pylon.core.block.base.PylonGuiBlock; +import io.github.pylonmc.pylon.core.block.context.BlockBreakContext; +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.fluid.FluidPointType; +import io.github.pylonmc.pylon.core.fluid.PylonFluid; +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.LogisticGroupType; +import io.github.pylonmc.pylon.core.util.MachineUpdateReason; +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.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.Arrays; +import java.util.List; +import java.util.Map; + +import static io.github.pylonmc.pylon.base.util.BaseUtils.baseKey; + + +public class CargoFluidAccumulator extends PylonBlock implements + PylonDirectionalBlock, + PylonGuiBlock, + PylonCargoBlock, + PylonFluidTank { + + public static final NamespacedKey ITEM_THRESHOLD_KEY = baseKey("item_threshold"); + public static final NamespacedKey FLUID_THRESHOLD_KEY = baseKey("fluid_threshold"); + public static final NamespacedKey ALLOW_FLUID_INPUTS_KEY = baseKey("allow_fluid_inputs"); + + private final VirtualInventory inputInventory = new VirtualInventory(5); + private final VirtualInventory outputInventory = new VirtualInventory(5); + + public final int fluidBuffer = getSettings().getOrThrow("fluid-buffer", ConfigAdapter.INT); + public final int transferRate = getSettings().getOrThrow("transfer-rate", ConfigAdapter.INT); + + public int itemThreshold; + public int fluidThreshold; + public boolean allowFluidInputs; + + public final ItemStackBuilder mainStack = ItemStackBuilder.of(Material.LIGHT_GRAY_CONCRETE) + .addCustomModelDataString(getKey() + ":main"); + public final ItemStackBuilder sideStack = ItemStackBuilder.of(Material.NOTE_BLOCK) + .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 final ItemStackBuilder itemThresholdButtonStack = ItemStackBuilder.gui(Material.WHITE_CONCRETE, getKey() + "item_threshold_button") + .lore(Component.translatable("pylon.pylonbase.gui.item_threshold_button.lore")); + public final ItemStackBuilder fluidThresholdButtonStack = ItemStackBuilder.gui(Material.WHITE_CONCRETE, getKey() + "fluid_threshold_button") + .lore(Component.translatable("pylon.pylonbase.gui.fluid_threshold_button.lore")); + + @Override + public void onBreak(@NotNull List<@NotNull ItemStack> drops, @NotNull BlockBreakContext context) { + PylonGuiBlock.super.onBreak(drops, context); + PylonFluidTank.super.onBreak(drops, context); + } + + public static class Item extends PylonItem { + + public final int fluidBuffer = getSettings().getOrThrow("fluid-buffer", ConfigAdapter.INT); + 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)) + ), + PylonArgument.of( + "fluid-buffer", + UnitFormat.MILLIBUCKETS_PER_SECOND.format(fluidBuffer) + ) + ); + } + } + + @SuppressWarnings("unused") + public CargoFluidAccumulator(@NotNull Block block, @NotNull BlockCreateContext context) { + super(block, context); + + setFacing(context.getFacing()); + + addCargoLogisticGroup(getFacing(), "input"); + addCargoLogisticGroup(getFacing().getOppositeFace(), "output"); + setCargoTransferRate(transferRate); + + createFluidPoint(FluidPointType.INPUT, BlockFace.EAST, context, false, 0.35F); + createFluidPoint(FluidPointType.OUTPUT, BlockFace.WEST, context, false, 0.35F); + setCapacity(fluidBuffer); + + addEntity("main", new ItemDisplayBuilder() + .itemStack(mainStack) + .transformation(new TransformBuilder() + .lookAlong(getFacing()) + .scale(0.7) + ) + .build(block.getLocation().toCenterLocation()) + ); + + addEntity("vertical", new ItemDisplayBuilder() + .itemStack(sideStack) + .transformation(new TransformBuilder() + .lookAlong(getFacing()) + .scale(0.55, 0.75, 0.55) + ) + .build(block.getLocation().toCenterLocation()) + ); + + addEntity("input", new ItemDisplayBuilder() + .itemStack(inputStack) + .transformation(new TransformBuilder() + .lookAlong(getFacing()) + .translate(0, 0, 0.2) + .scale(0.4, 0.4, 0.4) + ) + .build(block.getLocation().toCenterLocation()) + ); + + addEntity("output", new ItemDisplayBuilder() + .itemStack(outputStack) + .transformation(new TransformBuilder() + .lookAlong(getFacing()) + .translate(0, 0, -0.2) + .scale(0.4, 0.4, 0.4) + ) + .build(block.getLocation().toCenterLocation()) + ); + + itemThreshold = 1; + fluidThreshold = 10; + allowFluidInputs = true; + } + + @SuppressWarnings("unused") + public CargoFluidAccumulator(@NotNull Block block, @NotNull PersistentDataContainer pdc) { + super(block, pdc); + itemThreshold = pdc.get(ITEM_THRESHOLD_KEY, PylonSerializers.INTEGER); + fluidThreshold = pdc.get(FLUID_THRESHOLD_KEY, PylonSerializers.INTEGER); + allowFluidInputs = pdc.get(ALLOW_FLUID_INPUTS_KEY, PylonSerializers.BOOLEAN); + } + + @Override + public void write(@NotNull PersistentDataContainer pdc) { + pdc.set(ITEM_THRESHOLD_KEY, PylonSerializers.INTEGER, itemThreshold); + pdc.set(FLUID_THRESHOLD_KEY, PylonSerializers.INTEGER, fluidThreshold); + pdc.set(ALLOW_FLUID_INPUTS_KEY, PylonSerializers.BOOLEAN, allowFluidInputs); + } + + @Override + public double fluidAmountRequested(@NotNull PylonFluid fluid) { + return Math.max( + 0, + Math.min( + fluidThreshold - getFluidAmount(), + PylonFluidTank.super.fluidAmountRequested(fluid) + ) + ); + } + + @Override + public @NotNull Map<@NotNull PylonFluid, @NotNull Double> getSuppliedFluids() { + return allowFluidInputs ? Map.of() : PylonFluidTank.super.getSuppliedFluids(); + } + + @Override + public void onFluidRemoved(@NotNull PylonFluid fluid, double amount) { + PylonFluidTank.super.onFluidRemoved(fluid, amount); + doTransfer(); + } + + @Override + public void onFluidAdded(@NotNull PylonFluid fluid, double amount) { + PylonFluidTank.super.onFluidAdded(fluid, amount); + doTransfer(); + } + + @Override + public boolean isAllowedFluid(@NotNull PylonFluid fluid) { + return allowFluidInputs; + } + + @Override + public @NotNull Gui createGui() { + return Gui.normal() + .setStructure( + "# I i i i i i I #", + "# # # # # # # # #", + "# O o o o o o O #", + "# # # # # # # # #", + "# # # t # T # # #" + ) + .addIngredient('#', GuiItems.background()) + .addIngredient('i', inputInventory) + .addIngredient('I', GuiItems.input()) + .addIngredient('o', outputInventory) + .addIngredient('O', GuiItems.output()) + .addIngredient('t', new ItemThresholdButton()) + .addIngredient('T', new FluidThresholdButton()) + .build(); + } + + @Override + public void postInitialise() { + createLogisticGroup("input", LogisticGroupType.INPUT, inputInventory); + createLogisticGroup("output", LogisticGroupType.OUTPUT, outputInventory); + inputInventory.setPostUpdateHandler(event -> { + if (!(event.getUpdateReason() instanceof MachineUpdateReason)) { + doTransfer(); + } + }); + outputInventory.setPostUpdateHandler(event -> { + if (!(event.getUpdateReason() instanceof MachineUpdateReason)) { + doTransfer(); + } + }); + } + + @Override + public @Nullable WailaDisplay getWaila(@NotNull Player player) { + return new WailaDisplay(getDefaultWailaTranslationKey().arguments( + PylonArgument.of("item-threshold", UnitFormat.ITEMS.format(itemThreshold)), + PylonArgument.of("fluid-threshold", UnitFormat.MILLIBUCKETS.format(fluidThreshold)), + PylonArgument.of("bars", BaseUtils.createFluidAmountBar( + getFluidAmount(), + getFluidCapacity(), + 20, + TextColor.color(200, 255, 255) + )) + )); + } + + private void doTransfer() { + int inputTotal = 0; + for (ItemStack stack : inputInventory.getItems()) { + if (stack != null) { + inputTotal += stack.getAmount(); + } + } + + int outputTotal = 0; + for (ItemStack stack : outputInventory.getItems()) { + if (stack != null) { + outputTotal += stack.getAmount(); + } + } + + if (inputTotal >= itemThreshold) { + getLogisticGroupOrThrow("input").setFilter(stack -> false); + } + + if (outputTotal == 0 && getFluidAmount() < 1.0e-3) { + getLogisticGroupOrThrow("input").setFilter(null); + allowFluidInputs = true; + } + + if (inputTotal >= itemThreshold && getFluidAmount() >= (fluidThreshold - 1.0e-6)) { + List stacks = Arrays.stream(inputInventory.getItems()).toList(); + Preconditions.checkState(outputInventory.canHold(stacks)); + for (ItemStack stack : stacks) { + outputInventory.addItem(new MachineUpdateReason(), stack); + } + for (int slot = 0; slot < inputInventory.getItems().length; slot++) { + inputInventory.setItem(new MachineUpdateReason(), slot, null); + } + allowFluidInputs = false; + } + } + + public class ItemThresholdButton extends AbstractItem { + + @Override + public ItemProvider getItemProvider() { + return itemThresholdButtonStack + .name((Component.translatable("pylon.pylonbase.gui.item_threshold_button.name").arguments( + PylonArgument.of("threshold", itemThreshold) + ))); + } + + @Override + public void handleClick( + @NotNull ClickType clickType, + @NotNull Player player, + @NotNull InventoryClickEvent event + ) { + if (clickType.isLeftClick()) { + itemThreshold += 1; + } else { + itemThreshold = Math.max(1, itemThreshold - 1); + } + notifyWindows(); + doTransfer(); + } + } + + public class FluidThresholdButton extends AbstractItem { + + @Override + public ItemProvider getItemProvider() { + return fluidThresholdButtonStack + .name((Component.translatable("pylon.pylonbase.gui.fluid_threshold_button.name").arguments( + PylonArgument.of("threshold", fluidThreshold) + ))); + } + + @Override + public void handleClick( + @NotNull ClickType clickType, + @NotNull Player player, + @NotNull InventoryClickEvent event + ) { + int amount = clickType.isShiftClick() ? 100 : 10; + if (clickType.isLeftClick()) { + fluidThreshold = Math.min(fluidBuffer, fluidThreshold + amount); + } else { + fluidThreshold = Math.min(fluidBuffer, Math.max(10, fluidThreshold - amount)); + } + notifyWindows(); + doTransfer(); + } + } +} diff --git a/src/main/java/io/github/pylonmc/pylon/base/content/machines/cargo/CargoGate.java b/src/main/java/io/github/pylonmc/pylon/base/content/machines/cargo/CargoGate.java new file mode 100644 index 000000000..6e30f2305 --- /dev/null +++ b/src/main/java/io/github/pylonmc/pylon/base/content/machines/cargo/CargoGate.java @@ -0,0 +1,277 @@ +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.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.BlockDisplayBuilder; +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.LogisticGroupType; +import io.github.pylonmc.pylon.core.logistics.slot.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.BlockDisplay; +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.List; + +import static io.github.pylonmc.pylon.base.util.BaseUtils.baseKey; + + +public class CargoGate extends PylonBlock implements + PylonDirectionalBlock, + PylonGuiBlock, + PylonCargoBlock { + + public static final NamespacedKey THRESHOLD_KEY = baseKey("threshold"); + public static final NamespacedKey ITEMS_REMAINING_KEY = baseKey("items_remaining"); + + public final int transferRate = getSettings().getOrThrow("transfer-rate", ConfigAdapter.INT); + + public int threshold = 1; + public int itemsRemaining = 1; + + private final VirtualInventory outputInventory = 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 outputStack = ItemStackBuilder.of(Material.RED_TERRACOTTA) + .addCustomModelDataString(getKey() + ":output"); + public final ItemStackBuilder inputLeftStack = ItemStackBuilder.of(Material.YELLOW_CONCRETE) + .addCustomModelDataString(getKey() + ":input_left"); + public final ItemStackBuilder inputRightStack = ItemStackBuilder.of(Material.LIGHT_BLUE_CONCRETE) + .addCustomModelDataString(getKey() + ":input_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 thresholdButtonStack = ItemStackBuilder.gui(Material.WHITE_CONCRETE, getKey() + "threshold_button") + .lore(Component.translatable("pylon.pylonbase.gui.threshold_button.lore")); + + public class ThresholdButton extends AbstractItem { + + @Override + public ItemProvider getItemProvider() { + return thresholdButtonStack + .name((Component.translatable("pylon.pylonbase.gui.threshold_button.name").arguments( + PylonArgument.of("threshold", threshold) + ))); + } + + @Override + public void handleClick( + @NotNull ClickType clickType, + @NotNull Player player, + @NotNull InventoryClickEvent event + ) { + if (clickType.isLeftClick()) { + threshold += 1; + } else { + threshold = Math.max(1, threshold - 1); + } + itemsRemaining = threshold; + notifyWindows(); + } + } + + 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 CargoGate(@NotNull Block block, @NotNull BlockCreateContext context) { + super(block, context); + + setFacing(context.getFacing()); + + addCargoLogisticGroup(getFacing().getOppositeFace(), "output"); + 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.7) + ) + .build(block.getLocation().toCenterLocation()) + ); + + addEntity("repeater", new BlockDisplayBuilder() + .blockData(Material.REPEATER.createBlockData("[powered=false]")) + .transformation(new TransformBuilder() + .lookAlong(getFacing()) + .translate(0, 0.6, 0) + .scale(0.5) + ) + .build(block.getLocation().toCenterLocation()) + ); + + addEntity("input", new ItemDisplayBuilder() + .itemStack(outputStack) + .transformation(new TransformBuilder() + .lookAlong(getFacing()) + .translate(0, 0, -0.2) + .scale(0.4, 0.4, 0.4) + ) + .build(block.getLocation().toCenterLocation()) + ); + + addEntity("output_left", new ItemDisplayBuilder() + .itemStack(inputLeftStack) + .transformation(new TransformBuilder() + .lookAlong(getFacing()) + .translate(-0.2, 0, 0) + .scale(0.4, 0.4, 0.4) + ) + .build(block.getLocation().toCenterLocation()) + ); + + addEntity("output_right", new ItemDisplayBuilder() + .itemStack(inputRightStack) + .transformation(new TransformBuilder() + .lookAlong(getFacing()) + .translate(0.2, 0, 0) + .scale(0.4, 0.4, 0.4) + ) + .build(block.getLocation().toCenterLocation()) + ); + } + + @SuppressWarnings("unused") + public CargoGate(@NotNull Block block, @NotNull PersistentDataContainer pdc) { + super(block, pdc); + threshold = pdc.get(THRESHOLD_KEY, PylonSerializers.INTEGER); + itemsRemaining = pdc.get(ITEMS_REMAINING_KEY, PylonSerializers.INTEGER); + } + + @Override + public void write(@NotNull PersistentDataContainer pdc) { + pdc.set(THRESHOLD_KEY, PylonSerializers.INTEGER, threshold); + pdc.set(ITEMS_REMAINING_KEY, PylonSerializers.INTEGER, itemsRemaining); + } + + @Override + public @NotNull Gui createGui() { + return Gui.normal() + .setStructure( + "# L # # O # # R #", + "# l # # o # # r #", + "# L # # O # # R #", + "# # # # # # # # #", + "# # # # t # # # #", + "# # # # # # # # #" + ) + .addIngredient('#', GuiItems.background()) + .addIngredient('L', leftStack) + .addIngredient('l', leftInventory) + .addIngredient('O', GuiItems.output()) + .addIngredient('o', outputInventory) + .addIngredient('R', rightStack) + .addIngredient('r', rightInventory) + .addIngredient('t', new ThresholdButton()) + .build(); + } + + @Override + public void postInitialise() { + createLogisticGroup("output", LogisticGroupType.OUTPUT, new VirtualInventoryLogisticSlot(outputInventory, 0)); + createLogisticGroup("left", LogisticGroupType.INPUT, new VirtualInventoryLogisticSlot(leftInventory, 0)); + createLogisticGroup("right", LogisticGroupType.INPUT, new VirtualInventoryLogisticSlot(rightInventory, 0)); + outputInventory.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("threshold", threshold), + PylonArgument.of("side", itemsRemaining == 0 + ? Component.translatable("pylon.pylonbase.waila.cargo_splitter.left") + : Component.translatable("pylon.pylonbase.waila.cargo_splitter.right") + ) + )); + } + + private void doSplit() { + getHeldEntityOrThrow(BlockDisplay.class, "repeater") + .setBlock(Material.REPEATER.createBlockData("[powered=" + (itemsRemaining == 0 ? "true" : "false") + "]")); + + VirtualInventory inputInventory = itemsRemaining == 0 ? rightInventory : leftInventory; + ItemStack input = inputInventory.getItem(0); + if (input == null) { + return; + } + + ItemStack output = outputInventory.getItem(0); + if (output == null || (output.isSimilar(input) && output.getAmount() < output.getMaxStackSize())) { + if (output == null) { + outputInventory.setItem(new MachineUpdateReason(), 0, input.asOne()); + } else { + outputInventory.setItem(new MachineUpdateReason(), 0, output.add()); + } + inputInventory.setItem(new MachineUpdateReason(), 0, input.subtract()); + itemsRemaining = itemsRemaining == 0 + ? threshold + : itemsRemaining - 1; + doSplit(); + } + } +} 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 d78fc6314..e510c5df1 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 @@ -124,7 +124,7 @@ public void onDuctConnected(@NotNull PylonCargoConnectEvent event) { public void onDuctDisconnected(@NotNull PylonCargoDisconnectEvent event) { // Allow connecting to all faces now that there are zero connections List faces = PylonUtils.perpendicularImmediateFaces(getFacing()); - faces.add(getFacing().getOppositeFace()); + faces.add(getFacing()); for (BlockFace face : faces) { if (targetLogisticGroup != null) { addCargoLogisticGroup(face, targetLogisticGroup); 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 index ceac696f1..3ba72f365 100644 --- 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 @@ -39,7 +39,7 @@ public class CargoMerger extends PylonBlock public final ItemStackBuilder mainStack = ItemStackBuilder.of(Material.LIGHT_GRAY_CONCRETE) .addCustomModelDataString(getKey() + ":main"); - public final ItemStackBuilder verticalStack = ItemStackBuilder.of(Material.STRIPPED_WARPED_HYPHAE) + public final ItemStackBuilder verticalStack = ItemStackBuilder.of(Material.STRIPPED_WARPED_STEM) .addCustomModelDataString(getKey() + ":vertical"); public final ItemStackBuilder inputStack = ItemStackBuilder.of(Material.LIME_TERRACOTTA) .addCustomModelDataString(getKey() + ":input"); @@ -80,7 +80,7 @@ public CargoMerger(@NotNull Block block, @NotNull BlockCreateContext context) { .itemStack(mainStack) .transformation(new TransformBuilder() .lookAlong(getFacing()) - .scale(0.65) + .scale(0.7) ) .build(block.getLocation().toCenterLocation()) ); @@ -88,7 +88,9 @@ public CargoMerger(@NotNull Block block, @NotNull BlockCreateContext context) { addEntity("vertical", new ItemDisplayBuilder() .itemStack(verticalStack) .transformation(new TransformBuilder() - .scale(0.5, 0.7, 0.5) + .lookAlong(getFacing()) + .rotate(0, Math.PI / 4, 0) + .scale(0.45, 0.75, 0.45) ) .build(block.getLocation().toCenterLocation()) ); @@ -97,28 +99,17 @@ public CargoMerger(@NotNull Block block, @NotNull BlockCreateContext context) { .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) + .translate(0, 0, 0.2) .scale(0.4, 0.4, 0.4) ) .build(block.getLocation().toCenterLocation()) ); - addEntity("input_right", new ItemDisplayBuilder() + addEntity("input", new ItemDisplayBuilder() .itemStack(inputStack) .transformation(new TransformBuilder() .lookAlong(getFacing()) - .translate(0.15, 0, 0) - .scale(0.4, 0.4, 0.4) + .scale(0.8, 0.4, 0.4) ) .build(block.getLocation().toCenterLocation()) ); diff --git a/src/main/java/io/github/pylonmc/pylon/base/content/machines/cargo/CargoMeter.java b/src/main/java/io/github/pylonmc/pylon/base/content/machines/cargo/CargoMeter.java new file mode 100644 index 000000000..713d2f082 --- /dev/null +++ b/src/main/java/io/github/pylonmc/pylon/base/content/machines/cargo/CargoMeter.java @@ -0,0 +1,305 @@ +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.PylonCargoBlock; +import io.github.pylonmc.pylon.core.block.base.PylonDirectionalBlock; +import io.github.pylonmc.pylon.core.block.base.PylonGuiBlock; +import io.github.pylonmc.pylon.core.block.base.PylonTickingBlock; +import io.github.pylonmc.pylon.core.block.context.BlockCreateContext; +import io.github.pylonmc.pylon.core.config.PylonConfig; +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.TextDisplayBuilder; +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.LogisticGroupType; +import io.github.pylonmc.pylon.core.logistics.slot.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 org.bukkit.Color; +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.entity.TextDisplay; +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 org.joml.Vector3d; +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.time.Duration; +import java.util.ArrayList; +import java.util.List; + + +public class CargoMeter extends PylonBlock implements + PylonDirectionalBlock, + PylonGuiBlock, + PylonCargoBlock, + PylonTickingBlock { + + public static final NamespacedKey MEASUREMENTS_KEY = BaseUtils.baseKey("measurements"); + public static final NamespacedKey NUMBER_OF_MEASUREMENTS_KEY = BaseUtils.baseKey("number_of_measurements"); + + public final int transferRate = getSettings().getOrThrow("transfer-rate", ConfigAdapter.INT); + public final int minNumberOfMeasurements = getSettings().getOrThrow("min-number-of-measurements", ConfigAdapter.INT); + public final int maxNumberOfMeasurements = getSettings().getOrThrow("max-number-of-measurements", ConfigAdapter.INT); + + private int itemsAddedLastUpdate; + private final List measurements; + private int numberOfMeasurements; + + private final VirtualInventory inventory = new VirtualInventory(1); + + public final ItemStackBuilder mainStack = ItemStackBuilder.of(Material.LIGHT_GRAY_CONCRETE) + .addCustomModelDataString(getKey() + ":main"); + public final ItemStackBuilder side1Stack = ItemStackBuilder.of(Material.BARREL) + .addCustomModelDataString(getKey() + ":side1"); + public final ItemStackBuilder side2Stack = ItemStackBuilder.of(Material.BARREL) + .addCustomModelDataString(getKey() + ":side2"); + 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 final ItemStackBuilder projectorStack = ItemStackBuilder.of(Material.LIGHT_BLUE_STAINED_GLASS) + .addCustomModelDataString(getKey() + ":projector"); + + public static class Item extends PylonItem { + + public final int transferRate = getSettings().getOrThrow("transfer-rate", ConfigAdapter.INT); + public final int minNumberOfMeasurements = getSettings().getOrThrow("min-number-of-measurements", ConfigAdapter.INT); + public final int maxNumberOfMeasurements = getSettings().getOrThrow("max-number-of-measurements", 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)) + ), + PylonArgument.of( + "min-measurement-time", + UnitFormat.formatDuration(getDuration(minNumberOfMeasurements), true, true) + ), + PylonArgument.of( + "max-measurement-time", + UnitFormat.formatDuration(getDuration(maxNumberOfMeasurements), true, true) + ) + ); + } + } + + @SuppressWarnings("unused") + public CargoMeter(@NotNull Block block, @NotNull BlockCreateContext context) { + super(block, context); + + setFacing(context.getFacing()); + setTickInterval(PylonConfig.cargoTickInterval); + + addCargoLogisticGroup(getFacing(), "input"); + addCargoLogisticGroup(getFacing().getOppositeFace(), "output"); + setCargoTransferRate(transferRate); + + addEntity("main", new ItemDisplayBuilder() + .itemStack(mainStack) + .transformation(new TransformBuilder() + .lookAlong(getFacing()) + .scale(0.6) + ) + .build(block.getLocation().toCenterLocation()) + ); + + addEntity("side1", new ItemDisplayBuilder() + .itemStack(side1Stack) + .transformation(new TransformBuilder() + .lookAlong(getFacing()) + .rotate(Math.PI / 2, Math.PI / 2, 0) + .scale(0.45, 0.45, 0.65) + ) + .build(block.getLocation().toCenterLocation()) + ); + + addEntity("side2", new ItemDisplayBuilder() + .itemStack(side2Stack) + .transformation(new TransformBuilder() + .lookAlong(getFacing()) + .rotate(Math.PI / 2, Math.PI / 2, 0) + .scale(0.65, 0.45, 0.45) + ) + .build(block.getLocation().toCenterLocation()) + ); + + addEntity("projector", new ItemDisplayBuilder() + .itemStack(projectorStack) + .transformation(new TransformBuilder() + .lookAlong(getFacing()) + .translate(0, 0.25, 0) + .rotate(0, Math.PI / 4, 0) + .scale(0.3, 0.3, 0.3) + ) + .build(block.getLocation().toCenterLocation()) + ); + + addEntity("flow_rate", new TextDisplayBuilder() + .transformation(new TransformBuilder() + .translate(new Vector3d(0.0, 0.62, 0.0)) + .scale(0.6, 0.6, 0.6) + ) + .billboard(Display.Billboard.VERTICAL) + .backgroundColor(Color.fromARGB(0, 0, 0, 0)) + .text(UnitFormat.ITEMS_PER_SECOND.format(0).asComponent()) + .build(block.getLocation().toCenterLocation()) + ); + + addEntity("item", new ItemDisplayBuilder() + .transformation(new TransformBuilder() + .translate(new Vector3d(0.0, 0.53, 0.0)) + .scale(0.15, 0.15, 0.15) + ) + .itemStack(new ItemStack(Material.BARRIER)) + .billboard(Display.Billboard.VERTICAL) + .build(block.getLocation().toCenterLocation()) + ); + + addEntity("input", new ItemDisplayBuilder() + .itemStack(inputStack) + .transformation(new TransformBuilder() + .lookAlong(getFacing()) + .translate(0, 0, 0.125) + .scale(0.4, 0.4, 0.4) + ) + .build(block.getLocation().toCenterLocation()) + ); + + addEntity("output", new ItemDisplayBuilder() + .itemStack(outputStack) + .transformation(new TransformBuilder() + .lookAlong(getFacing()) + .translate(0, 0, -0.125) + .scale(0.4, 0.4, 0.4) + ) + .build(block.getLocation().toCenterLocation()) + ); + + setDisableBlockTextureEntity(true); + + measurements = new ArrayList<>(); + numberOfMeasurements = minNumberOfMeasurements; + } + + @SuppressWarnings("unused") + public CargoMeter(@NotNull Block block, @NotNull PersistentDataContainer pdc) { + super(block, pdc); + + setDisableBlockTextureEntity(true); + + measurements = new ArrayList<>(pdc.get(MEASUREMENTS_KEY, PylonSerializers.LIST.listTypeFrom(PylonSerializers.INTEGER))); + numberOfMeasurements = pdc.get(NUMBER_OF_MEASUREMENTS_KEY, PylonSerializers.INTEGER); + } + + @Override + public void write(@NotNull PersistentDataContainer pdc) { + pdc.set(MEASUREMENTS_KEY, PylonSerializers.LIST.listTypeFrom(PylonSerializers.INTEGER), measurements); + pdc.set(NUMBER_OF_MEASUREMENTS_KEY, PylonSerializers.INTEGER, numberOfMeasurements); + } + + @Override + public void postInitialise() { + createLogisticGroup("input", LogisticGroupType.INPUT, new VirtualInventoryLogisticSlot(inventory, 0)); + createLogisticGroup("output", LogisticGroupType.OUTPUT, new VirtualInventoryLogisticSlot(inventory, 0)); + inventory.setPostUpdateHandler(event -> { + ItemStack newStack = event.getNewItem(); + getHeldEntityOrThrow(ItemDisplay.class, "item") + .setItemStack(newStack == null ? new ItemStack(Material.BARRIER) : newStack); + if (event.isAdd()) { + itemsAddedLastUpdate += event.getAddedAmount(); + } + }); + } + + @Override + public @NotNull Gui createGui() { + return Gui.normal() + .setStructure( + "# # # # x # # # #", + "# # # # m # # # #" + ) + .addIngredient('#', GuiItems.background()) + .addIngredient('x', inventory) + .addIngredient('m', new MeasurementDurationItem()) + .build(); + } + + @Override + public void tick() { + measurements.add(itemsAddedLastUpdate); + itemsAddedLastUpdate = 0; + if (measurements.size() > numberOfMeasurements) { + for (int i = 0; i < measurements.size() - numberOfMeasurements; i++) { + measurements.removeFirst(); + } + } + double total = measurements.stream() + .mapToDouble(x -> x) + .sum(); + double average = (total / measurements.size()) * 20.0 / getTickInterval(); + Component component = UnitFormat.ITEMS_PER_SECOND.format(average).decimalPlaces(2).asComponent(); + getHeldEntityOrThrow(TextDisplay.class, "flow_rate").text(component); + } + + @Override + public @Nullable WailaDisplay getWaila(@NotNull Player player) { + return new WailaDisplay(getDefaultWailaTranslationKey().arguments( + PylonArgument.of("duration", UnitFormat.formatDuration(getDuration(numberOfMeasurements), true, true)) + )); + } + + public static Duration getDuration(int numberOfMeasurements) { + return Duration.ofMillis((long) numberOfMeasurements * PylonConfig.cargoTickInterval * 50); + } + + public class MeasurementDurationItem extends AbstractItem { + + @Override + public ItemProvider getItemProvider() { + return ItemStackBuilder.of(Material.WHITE_CONCRETE) + .name(Component.translatable("pylon.pylonbase.gui.fluid_meter.name").arguments( + PylonArgument.of("measurement-duration", UnitFormat.formatDuration(getDuration(numberOfMeasurements), true, true)) + )) + .lore(Component.translatable("pylon.pylonbase.gui.fluid_meter.lore")); + } + + @Override + public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { + int newValue; + if (clickType.isLeftClick()) { + newValue = numberOfMeasurements + (clickType.isShiftClick() ? 10 : 1); + } else if (clickType.isRightClick()) { + newValue = numberOfMeasurements + (clickType.isShiftClick() ? -10 : -1); + } else { + newValue = numberOfMeasurements; + } + numberOfMeasurements = Math.clamp(newValue, minNumberOfMeasurements, maxNumberOfMeasurements); + notifyWindows(); + } + } +} diff --git a/src/main/java/io/github/pylonmc/pylon/base/content/machines/cargo/CargoMonitor.java b/src/main/java/io/github/pylonmc/pylon/base/content/machines/cargo/CargoMonitor.java new file mode 100644 index 000000000..b7e4941f6 --- /dev/null +++ b/src/main/java/io/github/pylonmc/pylon/base/content/machines/cargo/CargoMonitor.java @@ -0,0 +1,196 @@ +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.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.TextDisplayBuilder; +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.LogisticGroupType; +import io.github.pylonmc.pylon.core.logistics.slot.VirtualInventoryLogisticSlot; +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.Color; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.entity.Display; +import org.bukkit.entity.ItemDisplay; +import org.bukkit.entity.TextDisplay; +import org.bukkit.inventory.ItemStack; +import org.bukkit.persistence.PersistentDataContainer; +import org.jetbrains.annotations.NotNull; +import org.joml.Vector3d; +import xyz.xenondevs.invui.gui.Gui; +import xyz.xenondevs.invui.inventory.VirtualInventory; + +import java.util.List; + + +public class CargoMonitor extends PylonBlock + implements PylonDirectionalBlock, PylonGuiBlock, PylonCargoBlock { + + private final VirtualInventory inventory = new VirtualInventory(1); + + public final int transferRate = getSettings().getOrThrow("transfer-rate", ConfigAdapter.INT); + + public final ItemStackBuilder mainStack = ItemStackBuilder.of(Material.LIGHT_GRAY_CONCRETE) + .addCustomModelDataString(getKey() + ":main"); + public final ItemStackBuilder side1Stack = ItemStackBuilder.of(Material.BARREL) + .addCustomModelDataString(getKey() + ":side1"); + public final ItemStackBuilder side2Stack = ItemStackBuilder.of(Material.BARREL) + .addCustomModelDataString(getKey() + ":side2"); + 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 final ItemStackBuilder projectorStack = ItemStackBuilder.of(Material.PINK_STAINED_GLASS) + .addCustomModelDataString(getKey() + ":projector"); + + 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 CargoMonitor(@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.6) + ) + .build(block.getLocation().toCenterLocation()) + ); + + addEntity("side1", new ItemDisplayBuilder() + .itemStack(side1Stack) + .transformation(new TransformBuilder() + .lookAlong(getFacing()) + .rotate(Math.PI / 2, Math.PI / 2, 0) + .scale(0.45, 0.45, 0.65) + ) + .build(block.getLocation().toCenterLocation()) + ); + + addEntity("side2", new ItemDisplayBuilder() + .itemStack(side2Stack) + .transformation(new TransformBuilder() + .lookAlong(getFacing()) + .rotate(Math.PI / 2, Math.PI / 2, 0) + .scale(0.65, 0.45, 0.45) + ) + .build(block.getLocation().toCenterLocation()) + ); + + addEntity("projector", new ItemDisplayBuilder() + .itemStack(projectorStack) + .transformation(new TransformBuilder() + .lookAlong(getFacing()) + .translate(0, 0.25, 0) + .rotate(0, Math.PI / 4, 0) + .scale(0.3, 0.3, 0.3) + ) + .build(block.getLocation().toCenterLocation()) + ); + + addEntity("amount", new TextDisplayBuilder() + .transformation(new TransformBuilder() + .translate(new Vector3d(0.0, 0.62, 0.0)) + .scale(0.6, 0.6, 0.6) + ) + .billboard(Display.Billboard.VERTICAL) + .backgroundColor(Color.fromARGB(0, 0, 0, 0)) + .text(Component.text(0)) + .build(block.getLocation().toCenterLocation()) + ); + + addEntity("item", new ItemDisplayBuilder() + .transformation(new TransformBuilder() + .translate(new Vector3d(0.0, 0.53, 0.0)) + .scale(0.15, 0.15, 0.15) + ) + .itemStack(new ItemStack(Material.BARRIER)) + .billboard(Display.Billboard.VERTICAL) + .build(block.getLocation().toCenterLocation()) + ); + + addEntity("input", new ItemDisplayBuilder() + .itemStack(inputStack) + .transformation(new TransformBuilder() + .lookAlong(getFacing()) + .translate(0, 0, 0.125) + .scale(0.4, 0.4, 0.4) + ) + .build(block.getLocation().toCenterLocation()) + ); + + addEntity("output", new ItemDisplayBuilder() + .itemStack(outputStack) + .transformation(new TransformBuilder() + .lookAlong(getFacing()) + .translate(0, 0, -0.125) + .scale(0.4, 0.4, 0.4) + ) + .build(block.getLocation().toCenterLocation()) + ); + } + + @SuppressWarnings("unused") + public CargoMonitor(@NotNull Block block, @NotNull PersistentDataContainer pdc) { + super(block, pdc); + } + + @Override + public void postInitialise() { + createLogisticGroup("input", LogisticGroupType.INPUT, new VirtualInventoryLogisticSlot(inventory, 0)); + createLogisticGroup("output", LogisticGroupType.OUTPUT, new VirtualInventoryLogisticSlot(inventory, 0)); + inventory.setPostUpdateHandler(event -> { + ItemStack newStack = event.getNewItem(); + if (newStack != null && !newStack.isEmpty()) { + getHeldEntityOrThrow(ItemDisplay.class, "item").setItemStack(event.getNewItem()); + getHeldEntityOrThrow(TextDisplay.class, "amount").text(Component.text(event.getNewItem().getAmount())); + } else { + getHeldEntityOrThrow(ItemDisplay.class, "item").setItemStack(new ItemStack(Material.BARRIER)); + getHeldEntityOrThrow(TextDisplay.class, "amount").text(Component.text(0)); + } + }); + } + + @Override + public @NotNull Gui createGui() { + return Gui.normal() + .setStructure("# # # # x # # # #") + .addIngredient('#', GuiItems.background()) + .addIngredient('x', inventory) + .build(); + } +} 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 index 90ba2d036..576eb69a6 100644 --- 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 @@ -68,7 +68,7 @@ public class CargoSplitter extends PylonBlock public final ItemStackBuilder mainStack = ItemStackBuilder.of(Material.LIGHT_GRAY_CONCRETE) .addCustomModelDataString(getKey() + ":main"); - public final ItemStackBuilder verticalStack = ItemStackBuilder.of(Material.STRIPPED_CRIMSON_HYPHAE) + public final ItemStackBuilder verticalStack = ItemStackBuilder.of(Material.STRIPPED_CRIMSON_STEM) .addCustomModelDataString(getKey() + ":vertical"); public final ItemStackBuilder inputStack = ItemStackBuilder.of(Material.LIME_TERRACOTTA) .addCustomModelDataString(getKey() + ":input"); @@ -160,7 +160,7 @@ public CargoSplitter(@NotNull Block block, @NotNull BlockCreateContext context) .itemStack(mainStack) .transformation(new TransformBuilder() .lookAlong(getFacing()) - .scale(0.65) + .scale(0.7) ) .build(block.getLocation().toCenterLocation()) ); @@ -168,7 +168,9 @@ public CargoSplitter(@NotNull Block block, @NotNull BlockCreateContext context) addEntity("vertical", new ItemDisplayBuilder() .itemStack(verticalStack) .transformation(new TransformBuilder() - .scale(0.5, 0.7, 0.5) + .lookAlong(getFacing()) + .rotate(0, Math.PI / 4, 0) + .scale(0.45, 0.75, 0.45) ) .build(block.getLocation().toCenterLocation()) ); @@ -177,7 +179,7 @@ public CargoSplitter(@NotNull Block block, @NotNull BlockCreateContext context) .itemStack(inputStack) .transformation(new TransformBuilder() .lookAlong(getFacing()) - .translate(0, 0, 0.15) + .translate(0, 0, 0.2) .scale(0.4, 0.4, 0.4) ) .build(block.getLocation().toCenterLocation()) @@ -187,7 +189,7 @@ public CargoSplitter(@NotNull Block block, @NotNull BlockCreateContext context) .itemStack(outputLeftStack) .transformation(new TransformBuilder() .lookAlong(getFacing()) - .translate(-0.15, 0, 0) + .translate(-0.2, 0, 0) .scale(0.4, 0.4, 0.4) ) .build(block.getLocation().toCenterLocation()) @@ -197,7 +199,7 @@ public CargoSplitter(@NotNull Block block, @NotNull BlockCreateContext context) .itemStack(outputRightStack) .transformation(new TransformBuilder() .lookAlong(getFacing()) - .translate(0.15, 0, 0) + .translate(0.2, 0, 0) .scale(0.4, 0.4, 0.4) ) .build(block.getLocation().toCenterLocation()) 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 index 8ff1e3435..f50a586cf 100644 --- 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 @@ -46,8 +46,6 @@ public class CargoValve extends PylonBlock implements PylonCargoBlock { 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); @@ -55,14 +53,14 @@ public class CargoValve extends PylonBlock implements 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 final ItemStackBuilder stackOff = ItemStackBuilder.of(Material.CYAN_TERRACOTTA) + .addCustomModelDataString(getKey() + ":stack_off"); + public final ItemStackBuilder stackOn = ItemStackBuilder.of(Material.WHITE_CONCRETE) + .addCustomModelDataString(getKey() + ":stack_on"); public static class Item extends PylonItem { @@ -91,33 +89,12 @@ public CargoValve(@NotNull Block block, @NotNull BlockCreateContext context) { addCargoLogisticGroup(getFacing(), "input"); addCargoLogisticGroup(getFacing().getOppositeFace(), "output"); - setCargoTransferRate(transferRate); addEntity("main", new ItemDisplayBuilder() - .itemStack(mainStack) + .itemStack(stackOff) .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) + .scale(0.5, 0.5, 0.65) ) .build(block.getLocation().toCenterLocation()) ); @@ -143,6 +120,7 @@ public CargoValve(@NotNull Block block, @NotNull BlockCreateContext context) { ); enabled = false; + setCargoTransferRate(0); } @SuppressWarnings("unused") @@ -179,10 +157,8 @@ public void onInteract(@NotNull PlayerInteractEvent event) { 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)); + getHeldEntityOrThrow(ItemDisplay.class, "main") + .setItemStack((enabled ? stackOn : stackOff).build()); } @Override diff --git a/src/main/java/io/github/pylonmc/pylon/base/content/machines/fluid/CreativeFluidSource.java b/src/main/java/io/github/pylonmc/pylon/base/content/machines/fluid/CreativeFluidSource.java index d16f76477..38512044b 100644 --- a/src/main/java/io/github/pylonmc/pylon/base/content/machines/fluid/CreativeFluidSource.java +++ b/src/main/java/io/github/pylonmc/pylon/base/content/machines/fluid/CreativeFluidSource.java @@ -38,8 +38,7 @@ public class CreativeFluidSource extends PylonBlock implements PylonFluidBlock, PylonDirectionalBlock, - PylonGuiBlock, - PylonEntityHolderBlock { + PylonGuiBlock { public static final NamespacedKey FLUID_KEY = baseKey("fluid"); @@ -51,7 +50,7 @@ public CreativeFluidSource(@NotNull Block block, @NotNull BlockCreateContext con setFacing(context.getFacing()); createFluidPoint(FluidPointType.OUTPUT, BlockFace.NORTH, context, true, 0.55F); addEntity("fluid-1", new ItemDisplayBuilder() - .material(Material.RED_CONCRETE) + .material(Material.RED_TERRACOTTA) .transformation(new TransformBuilder() .translate(0, -0.5, 0) .scale(1.1, 0.8, 0.8) @@ -59,7 +58,7 @@ public CreativeFluidSource(@NotNull Block block, @NotNull BlockCreateContext con .build(getBlock().getLocation().toCenterLocation().add(0, 0.5, 0)) ); addEntity("fluid-2", new ItemDisplayBuilder() - .material(Material.RED_CONCRETE) + .material(Material.RED_TERRACOTTA) .transformation(new TransformBuilder() .translate(0, -0.5, 0) .scale(0.8, 1.1, 0.8) @@ -67,7 +66,7 @@ public CreativeFluidSource(@NotNull Block block, @NotNull BlockCreateContext con .build(getBlock().getLocation().toCenterLocation().add(0, 0.5, 0)) ); addEntity("fluid-3", new ItemDisplayBuilder() - .material(Material.RED_CONCRETE) + .material(Material.RED_TERRACOTTA) .transformation(new TransformBuilder() .translate(0, -0.5, 0) .scale(0.8, 0.8, 1.1) @@ -100,7 +99,7 @@ public void onFluidRemoved(@NotNull PylonFluid fluid, double amount) {} public @NotNull Gui createGui() { return (FluidSelector.make(() -> fluid, fluid -> { this.fluid = fluid; - ItemStack stack = fluid == null ? new ItemStack(Material.RED_CONCRETE) : fluid.getItem(); + ItemStack stack = fluid == null ? new ItemStack(Material.RED_TERRACOTTA) : fluid.getItem(); getHeldEntityOrThrow(ItemDisplay.class, "fluid-1").setItemStack(stack); getHeldEntityOrThrow(ItemDisplay.class, "fluid-2").setItemStack(stack); getHeldEntityOrThrow(ItemDisplay.class, "fluid-3").setItemStack(stack); diff --git a/src/main/java/io/github/pylonmc/pylon/base/content/machines/fluid/FluidFilter.java b/src/main/java/io/github/pylonmc/pylon/base/content/machines/fluid/FluidFilter.java index 7e8663341..563c57888 100644 --- a/src/main/java/io/github/pylonmc/pylon/base/content/machines/fluid/FluidFilter.java +++ b/src/main/java/io/github/pylonmc/pylon/base/content/machines/fluid/FluidFilter.java @@ -66,7 +66,7 @@ public Item(@NotNull ItemStack stack) { public final ItemStackBuilder mainStack = ItemStackBuilder.of(Material.WHITE_CONCRETE) .addCustomModelDataString(getKey() + ":main"); - public final ItemStack noFluidStack = ItemStackBuilder.of(Material.RED_CONCRETE) + public final ItemStack noFluidStack = ItemStackBuilder.of(Material.RED_TERRACOTTA) .addCustomModelDataString(getKey() + ":fluid:none") .build(); @@ -84,15 +84,23 @@ public FluidFilter(@NotNull Block block, @NotNull BlockCreateContext context) { .itemStack(mainStack) .transformation(new TransformBuilder() .lookAlong(getFacing()) - .scale(0.25, 0.25, 0.5) + .scale(0.35, 0.35, 0.5) ) .build(block.getLocation().toCenterLocation()) ); - addEntity("fluid", new ItemDisplayBuilder() + addEntity("fluid1", new ItemDisplayBuilder() .itemStack(noFluidStack) .transformation(new TransformBuilder() .lookAlong(getFacing()) - .scale(0.2, 0.3, 0.45) + .scale(0.3, 0.4, 0.45) + ) + .build(block.getLocation().toCenterLocation()) + ); + addEntity("fluid2", new ItemDisplayBuilder() + .itemStack(noFluidStack) + .transformation(new TransformBuilder() + .lookAlong(getFacing()) + .scale(0.4, 0.3, 0.45) ) .build(block.getLocation().toCenterLocation()) ); @@ -129,18 +137,16 @@ public void write(@NotNull PersistentDataContainer pdc) { )); } - private @NotNull ItemDisplay getFluidDisplay() { - return getHeldEntityOrThrow(ItemDisplay.class, "fluid"); - } - @Override public boolean isAllowedFluid(@NotNull PylonFluid fluid) { - return fluid == this.fluid; + return fluid.equals(this.fluid); } public void setFluid(PylonFluid fluid) { this.fluid = fluid; - getFluidDisplay().setItemStack(fluid == null ? noFluidStack : fluid.getItem()); + ItemStack stack = fluid == null ? noFluidStack : fluid.getItem(); + getHeldEntityOrThrow(ItemDisplay.class, "fluid1").setItemStack(stack); + getHeldEntityOrThrow(ItemDisplay.class, "fluid2").setItemStack(stack); } @Override diff --git a/src/main/java/io/github/pylonmc/pylon/base/content/machines/fluid/FluidMeter.java b/src/main/java/io/github/pylonmc/pylon/base/content/machines/fluid/FluidMeter.java index 503f519d8..eb10f8f68 100644 --- a/src/main/java/io/github/pylonmc/pylon/base/content/machines/fluid/FluidMeter.java +++ b/src/main/java/io/github/pylonmc/pylon/base/content/machines/fluid/FluidMeter.java @@ -1,81 +1,274 @@ package io.github.pylonmc.pylon.base.content.machines.fluid; +import io.github.pylonmc.pylon.base.util.BaseUtils; +import io.github.pylonmc.pylon.core.block.PylonBlock; +import io.github.pylonmc.pylon.core.block.base.PylonDirectionalBlock; +import io.github.pylonmc.pylon.core.block.base.PylonFluidTank; +import io.github.pylonmc.pylon.core.block.base.PylonGuiBlock; import io.github.pylonmc.pylon.core.block.base.PylonTickingBlock; +import io.github.pylonmc.pylon.core.block.context.BlockBreakContext; import io.github.pylonmc.pylon.core.block.context.BlockCreateContext; import io.github.pylonmc.pylon.core.config.PylonConfig; 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.TextDisplayBuilder; import io.github.pylonmc.pylon.core.entity.display.transform.TransformBuilder; +import io.github.pylonmc.pylon.core.fluid.FluidPointType; import io.github.pylonmc.pylon.core.fluid.PylonFluid; -import io.github.pylonmc.pylon.core.util.PylonUtils; +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.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.Bukkit; import org.bukkit.Color; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; +import org.bukkit.entity.Display; +import org.bukkit.entity.ItemDisplay; +import org.bukkit.entity.Player; import org.bukkit.entity.TextDisplay; +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 org.joml.Vector3d; +import xyz.xenondevs.invui.gui.Gui; +import xyz.xenondevs.invui.item.ItemProvider; +import xyz.xenondevs.invui.item.impl.AbstractItem; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; -public class FluidMeter extends FluidFilter implements PylonTickingBlock { - public final int tickInterval = getSettings().getOrThrow("tick-interval", ConfigAdapter.INT); +public class FluidMeter extends PylonBlock implements + PylonFluidTank, + PylonDirectionalBlock, + PylonTickingBlock, + PylonGuiBlock { - private double removedSinceLastUpdate; + public static final NamespacedKey MEASUREMENTS_KEY = BaseUtils.baseKey("measurements"); + public static final NamespacedKey NUMBER_OF_MEASUREMENTS_KEY = BaseUtils.baseKey("number_of_measurements"); + + public final double buffer = getSettings().getOrThrow("buffer", ConfigAdapter.DOUBLE); + public final int minNumberOfMeasurements = getSettings().getOrThrow("min-number-of-measurements", ConfigAdapter.INT); + public final int maxNumberOfMeasurements = getSettings().getOrThrow("max-number-of-measurements", ConfigAdapter.INT); + + private double fluidAddedLastUpdate; + private final List measurements; + private int numberOfMeasurements; + + public static class Item extends PylonItem { + + public final double buffer = getSettings().getOrThrow("buffer", ConfigAdapter.DOUBLE); + public final int minNumberOfMeasurements = getSettings().getOrThrow("min-number-of-measurements", ConfigAdapter.INT); + public final int maxNumberOfMeasurements = getSettings().getOrThrow("max-number-of-measurements", ConfigAdapter.INT); + + public Item(@NotNull ItemStack stack) { + super(stack); + } + + @Override + public @NotNull List getPlaceholders() { + return List.of( + PylonArgument.of("buffer", UnitFormat.MILLIBUCKETS.format(buffer)), + PylonArgument.of( + "min-measurement-time", + UnitFormat.formatDuration(getDuration(minNumberOfMeasurements), true, true) + ), + PylonArgument.of( + "max-measurement-time", + UnitFormat.formatDuration(getDuration(maxNumberOfMeasurements), true, true) + ) + ); + } + } + + public final ItemStackBuilder mainStack = ItemStackBuilder.of(Material.WHITE_CONCRETE) + .addCustomModelDataString(getKey() + ":main"); + public final ItemStackBuilder topStack = ItemStackBuilder.of(Material.LIGHT_GRAY_CONCRETE) + .addCustomModelDataString(getKey() + ":top"); + public final ItemStackBuilder projectorStack = ItemStackBuilder.of(Material.LIGHT_BLUE_STAINED_GLASS) + .addCustomModelDataString(getKey() + ":projector"); @SuppressWarnings("unused") public FluidMeter(@NotNull Block block, @NotNull BlockCreateContext context) { super(block, context); - setTickInterval(tickInterval); + setFacing(context.getFacing()); + setCapacity(buffer); + setTickInterval(PylonConfig.fluidTickInterval); + + createFluidPoint(FluidPointType.INPUT, BlockFace.NORTH, context, false, 0.25F); + createFluidPoint(FluidPointType.OUTPUT, BlockFace.SOUTH, context, false, 0.25F); - addEntity("flow_rate_north", createTextDisplay(BlockFace.WEST)); - addEntity("flow_rate_south", createTextDisplay(BlockFace.EAST)); + addEntity("main", new ItemDisplayBuilder() + .itemStack(mainStack) + .transformation(new TransformBuilder() + .lookAlong(getFacing()) + .scale(0.25, 0.25, 0.5) + ) + .build(block.getLocation().toCenterLocation()) + ); + addEntity("top", new ItemDisplayBuilder() + .itemStack(topStack) + .transformation(new TransformBuilder() + .lookAlong(getFacing()) + .scale(0.2, 0.3, 0.45) + ) + .build(block.getLocation().toCenterLocation()) + ); + + addEntity("projector", new ItemDisplayBuilder() + .itemStack(projectorStack) + .transformation(new TransformBuilder() + .lookAlong(getFacing()) + .translate(0, 0.125, 0) + .rotate(0, Math.PI / 4, 0) + .scale(0.12, 0.12, 0.12) + ) + .build(block.getLocation().toCenterLocation()) + ); + + addEntity("flow_rate", new TextDisplayBuilder() + .transformation(new TransformBuilder() + .translate(new Vector3d(0.0, 0.35, 0.0)) + .scale(0.3, 0.3, 0.3) + ) + .billboard(Display.Billboard.VERTICAL) + .backgroundColor(Color.fromARGB(0, 0, 0, 0)) + .text(UnitFormat.MILLIBUCKETS_PER_SECOND.format(0).asComponent()) + .build(block.getLocation().toCenterLocation()) + ); + addEntity("fluid", new ItemDisplayBuilder() + .transformation(new TransformBuilder() + .translate(new Vector3d(0.0, 0.3, 0.0)) + .scale(0.07, 0.07, 0.07) + ) + .itemStack(new ItemStack(Material.BARRIER)) + .billboard(Display.Billboard.VERTICAL) + .build(block.getLocation().toCenterLocation()) + ); - removedSinceLastUpdate = 0.0; setDisableBlockTextureEntity(true); + + measurements = new ArrayList<>(); + numberOfMeasurements = minNumberOfMeasurements; } @SuppressWarnings("unused") public FluidMeter(@NotNull Block block, @NotNull PersistentDataContainer pdc) { super(block, pdc); - removedSinceLastUpdate = 0.0; + setDisableBlockTextureEntity(true); + + measurements = new ArrayList<>(pdc.get(MEASUREMENTS_KEY, PylonSerializers.LIST.listTypeFrom(PylonSerializers.DOUBLE))); + numberOfMeasurements = pdc.get(NUMBER_OF_MEASUREMENTS_KEY, PylonSerializers.INTEGER); } @Override - public void onFluidRemoved(@NotNull PylonFluid fluid, double amount) { - super.onFluidRemoved(fluid, amount); - removedSinceLastUpdate += amount; + public void write(@NotNull PersistentDataContainer pdc) { + pdc.set(MEASUREMENTS_KEY, PylonSerializers.LIST.listTypeFrom(PylonSerializers.DOUBLE), measurements); + pdc.set(NUMBER_OF_MEASUREMENTS_KEY, PylonSerializers.INTEGER, numberOfMeasurements); } @Override - public void tick() { - Component component = UnitFormat.MILLIBUCKETS_PER_SECOND.format(Math.round(removedSinceLastUpdate / (20 * PylonConfig.FLUID_TICK_INTERVAL))).asComponent(); + public @NotNull Gui createGui() { + return Gui.normal() + .setStructure("# # # # m # # # #") + .addIngredient('#', GuiItems.background()) + .addIngredient('m', new MeasurementDurationItem()) + .build(); + } - getHeldEntityOrThrow(TextDisplay.class, "flow_rate_north").text(component); - getHeldEntityOrThrow(TextDisplay.class, "flow_rate_south").text(component); + @Override + public void onFluidAdded(@NotNull PylonFluid fluid, double amount) { + PylonFluidTank.super.onFluidAdded(fluid, amount); + fluidAddedLastUpdate = amount; + getHeldEntityOrThrow(ItemDisplay.class, "fluid").setItemStack(fluid.getItem()); + } - removedSinceLastUpdate = 0.0; + @Override + public void tick() { + measurements.add(fluidAddedLastUpdate); + fluidAddedLastUpdate = 0.0; + if (measurements.size() > numberOfMeasurements) { + for (int i = 0; i < measurements.size() - numberOfMeasurements; i++) { + measurements.removeFirst(); + } + } + double total = measurements.stream() + .mapToDouble(x -> x) + .sum(); + double average = (total / measurements.size()) * 20.0 / getTickInterval(); + Component component = UnitFormat.MILLIBUCKETS_PER_SECOND.format(average).decimalPlaces(0).asComponent(); + getHeldEntityOrThrow(TextDisplay.class, "flow_rate").text(component); } - private @NotNull TextDisplay createTextDisplay(@NotNull BlockFace face) { - return new TextDisplayBuilder() - .transformation(new TransformBuilder() - .lookAlong(PylonUtils.rotateFaceToReference(getFacing(), face)) - .translate(new Vector3d(0.0, 0.0, 0.126)) - .scale(0.3, 0.3, 0.0001) - ) - .backgroundColor(Color.fromARGB(0, 0, 0, 0)) - .text(UnitFormat.MILLIBUCKETS_PER_SECOND.format(0).asComponent()) - .build(getBlock().getLocation().toCenterLocation()); + @Override + public void onBreak(@NotNull List<@NotNull ItemStack> drops, @NotNull BlockBreakContext context) { + PylonFluidTank.super.onBreak(drops, context); + PylonGuiBlock.super.onBreak(drops, context); + } + + @Override + public boolean isAllowedFluid(@NotNull PylonFluid fluid) { + return true; } @Override - public @NotNull Component getGuiTitle() { - return Component.translatable("pylon.pylonbase.item.fluid_meter.gui"); + public @Nullable WailaDisplay getWaila(@NotNull Player player) { + return new WailaDisplay(getDefaultWailaTranslationKey().arguments( + PylonArgument.of("bars", BaseUtils.createFluidAmountBar( + getFluidAmount(), + getFluidCapacity(), + 20, + TextColor.color(200, 255, 255) + )), + PylonArgument.of("fluid", getFluidType() == null + ? Component.translatable("pylon.pylonbase.fluid.none") + : getFluidType().getName() + ), + PylonArgument.of("duration", UnitFormat.formatDuration(getDuration(numberOfMeasurements), true, true)) + )); } + public static Duration getDuration(int numberOfMeasurements) { + return Duration.ofMillis((long) numberOfMeasurements * PylonConfig.fluidTickInterval * 50); + } + + public class MeasurementDurationItem extends AbstractItem { + + @Override + public ItemProvider getItemProvider() { + return ItemStackBuilder.of(Material.WHITE_CONCRETE) + .name(Component.translatable("pylon.pylonbase.gui.fluid_meter.name").arguments( + PylonArgument.of("measurement-duration", UnitFormat.formatDuration(getDuration(numberOfMeasurements), true, true)) + )) + .lore(Component.translatable("pylon.pylonbase.gui.fluid_meter.lore")); + } + + @Override + public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { + int newValue; + if (clickType.isLeftClick()) { + newValue = numberOfMeasurements + (clickType.isShiftClick() ? 10 : 1); + } else if (clickType.isRightClick()) { + newValue = numberOfMeasurements + (clickType.isShiftClick() ? -10 : -1); + } else { + newValue = numberOfMeasurements; + } + numberOfMeasurements = Math.clamp(newValue, minNumberOfMeasurements, maxNumberOfMeasurements); + notifyWindows(); + } + } } diff --git a/src/main/java/io/github/pylonmc/pylon/base/content/machines/fluid/FluidTank.java b/src/main/java/io/github/pylonmc/pylon/base/content/machines/fluid/FluidTank.java index 9443271ba..9bb52d1c9 100644 --- a/src/main/java/io/github/pylonmc/pylon/base/content/machines/fluid/FluidTank.java +++ b/src/main/java/io/github/pylonmc/pylon/base/content/machines/fluid/FluidTank.java @@ -3,6 +3,7 @@ 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.PylonDirectionalBlock; import io.github.pylonmc.pylon.core.block.base.PylonEntityHolderBlock; import io.github.pylonmc.pylon.core.block.base.PylonFluidTank; import io.github.pylonmc.pylon.core.block.base.PylonMultiblock; @@ -37,7 +38,7 @@ import java.util.Set; public class FluidTank extends PylonBlock - implements PylonMultiblock, PylonFluidTank, PylonEntityHolderBlock { + implements PylonMultiblock, PylonFluidTank, PylonDirectionalBlock { private final int maxHeight = getSettings().getOrThrow("max-height", ConfigAdapter.INT); @@ -65,6 +66,7 @@ public Item(@NotNull ItemStack stack) { @SuppressWarnings("unused") public FluidTank(@NotNull Block block, @NotNull BlockCreateContext context) { super(block, context); + setFacing(context.getFacing()); addEntity("fluid", new ItemDisplayBuilder() .build(getBlock().getLocation().toCenterLocation().add(0, 1, 0)) ); @@ -136,6 +138,7 @@ public void onMultiblockUnformed(boolean partUnloaded) { allowedTemperatures.clear(); if (!partUnloaded) { setCapacity(0); + setFluid(0); setFluidType(null); } } diff --git a/src/main/java/io/github/pylonmc/pylon/base/content/machines/fluid/FluidValve.java b/src/main/java/io/github/pylonmc/pylon/base/content/machines/fluid/FluidValve.java index 4db0c7ef3..2ed179695 100644 --- a/src/main/java/io/github/pylonmc/pylon/base/content/machines/fluid/FluidValve.java +++ b/src/main/java/io/github/pylonmc/pylon/base/content/machines/fluid/FluidValve.java @@ -46,8 +46,6 @@ public class FluidValve extends PylonBlock implements PylonFluidTank, PylonInteractBlock, PylonDirectionalBlock { public static final NamespacedKey ENABLED_KEY = baseKey("enabled"); - public static final int BRIGHTNESS_OFF = 6; - public static final int BRIGHTNESS_ON = 13; private boolean enabled; @@ -69,6 +67,11 @@ public Item(@NotNull ItemStack stack) { } } + public final ItemStackBuilder stackOff = ItemStackBuilder.of(Material.CYAN_TERRACOTTA) + .addCustomModelDataString(getKey() + ":stack_off"); + public final ItemStackBuilder stackOn = ItemStackBuilder.of(Material.WHITE_CONCRETE) + .addCustomModelDataString(getKey() + ":stack_on"); + @SuppressWarnings("unused") public FluidValve(@NotNull Block block, @NotNull BlockCreateContext context) { super(block); @@ -78,14 +81,10 @@ public FluidValve(@NotNull Block block, @NotNull BlockCreateContext context) { createFluidPoint(FluidPointType.INPUT, BlockFace.NORTH, context, false, 0.25F); createFluidPoint(FluidPointType.OUTPUT, BlockFace.SOUTH, context, false, 0.25F); addEntity("main", new ItemDisplayBuilder() - .itemStack(ItemStackBuilder.of(Material.WHITE_CONCRETE) - .addCustomModelDataString(getKey() + ":main") - .build() - ) - .brightness(BRIGHTNESS_OFF) + .itemStack(stackOff) .transformation(new TransformBuilder() .lookAlong(PylonUtils.rotateFaceToReference(getFacing(), BlockFace.NORTH).getDirection().toVector3d()) - .scale(0.25, 0.25, 0.5) + .scale(0.2, 0.2, 0.5) ) .build(getBlock().getLocation().toCenterLocation()) ); @@ -110,7 +109,7 @@ public void write(@NotNull PersistentDataContainer pdc) { @Override public void onInteract(@NotNull PlayerInteractEvent event) { - if (event.getAction() != Action.RIGHT_CLICK_BLOCK || event.getHand() != EquipmentSlot.HAND) { + if (event.getAction() != Action.RIGHT_CLICK_BLOCK || event.getHand() != EquipmentSlot.HAND || event.getPlayer().isSneaking()) { return; } @@ -119,7 +118,7 @@ public void onInteract(@NotNull PlayerInteractEvent event) { enabled = !enabled; getHeldEntityOrThrow(ItemDisplay.class, "main") - .setBrightness(new Display.Brightness(0, enabled ? BRIGHTNESS_ON : BRIGHTNESS_OFF)); + .setItemStack((enabled ? stackOn : stackOff).build()); } @Override diff --git a/src/main/java/io/github/pylonmc/pylon/base/content/machines/hydraulics/HydraulicFarmer.java b/src/main/java/io/github/pylonmc/pylon/base/content/machines/hydraulics/HydraulicFarmer.java index d9a9d877a..013301c72 100644 --- a/src/main/java/io/github/pylonmc/pylon/base/content/machines/hydraulics/HydraulicFarmer.java +++ b/src/main/java/io/github/pylonmc/pylon/base/content/machines/hydraulics/HydraulicFarmer.java @@ -128,7 +128,7 @@ public void tick() { if (!CROPS_TO_BREAK.contains(cropType)) continue; - if (cropBlock.getBlockData() instanceof Ageable ageable) { + if (cropBlock.getType() != Material.SUGAR_CANE && cropBlock.getBlockData() instanceof Ageable ageable) { if (ageable.getAge() == ageable.getMaximumAge()) { cropBlock.breakNaturally(); removeFluid(BaseFluids.HYDRAULIC_FLUID, hydraulicFluidUsed); diff --git a/src/main/java/io/github/pylonmc/pylon/base/content/machines/hydraulics/SolarLens.java b/src/main/java/io/github/pylonmc/pylon/base/content/machines/hydraulics/SolarLens.java new file mode 100644 index 000000000..4aa33e680 --- /dev/null +++ b/src/main/java/io/github/pylonmc/pylon/base/content/machines/hydraulics/SolarLens.java @@ -0,0 +1,35 @@ +package io.github.pylonmc.pylon.base.content.machines.hydraulics; + +import io.github.pylonmc.pylon.core.block.PylonBlock; +import io.github.pylonmc.pylon.core.block.context.BlockCreateContext; +import io.github.pylonmc.pylon.core.util.PylonUtils; +import kotlin.Pair; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.data.Directional; +import org.bukkit.persistence.PersistentDataContainer; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; +import java.util.Set; + + +public class SolarLens extends PylonBlock { + public SolarLens(@NotNull Block block, @NotNull BlockCreateContext context) { + super(block, context); + } + + public SolarLens(@NotNull Block block, @NotNull PersistentDataContainer pdc) { + super(block, pdc); + } + + @Override + public @NotNull Map> getBlockTextureProperties() { + Map> properties = super.getBlockTextureProperties(); + Set faces = ((Directional) getBlock().getState()).getFaces(); + for (BlockFace face : PylonUtils.CARDINAL_FACES) { + properties.put(face.name(), new Pair<>(faces.contains(face) ? "true" : "false", 2)); + } + return properties; + } +} diff --git a/src/main/java/io/github/pylonmc/pylon/base/content/machines/simple/Press.java b/src/main/java/io/github/pylonmc/pylon/base/content/machines/simple/Press.java index 4ebeae443..ea06cf4f1 100644 --- a/src/main/java/io/github/pylonmc/pylon/base/content/machines/simple/Press.java +++ b/src/main/java/io/github/pylonmc/pylon/base/content/machines/simple/Press.java @@ -84,7 +84,10 @@ public Press(@NotNull Block block, @NotNull BlockCreateContext context) { @SuppressWarnings("unused") public Press(@NotNull Block block, @NotNull PersistentDataContainer pdc) { super(block); + } + @Override + public void postLoad() { if (isProcessingRecipe()) { finishRecipe(); } diff --git a/src/main/java/io/github/pylonmc/pylon/base/content/machines/smelting/PitKiln.java b/src/main/java/io/github/pylonmc/pylon/base/content/machines/smelting/PitKiln.java index d23afa511..59edd84d8 100644 --- a/src/main/java/io/github/pylonmc/pylon/base/content/machines/smelting/PitKiln.java +++ b/src/main/java/io/github/pylonmc/pylon/base/content/machines/smelting/PitKiln.java @@ -62,7 +62,7 @@ public Item(@NotNull ItemStack stack) { public @NotNull List getPlaceholders() { return List.of( PylonArgument.of("capacity", CAPACITY), - PylonArgument.of("smelting_time", UnitFormat.formatDuration(Duration.ofSeconds(PROCESSING_TIME_SECONDS))), + PylonArgument.of("smelting_time", UnitFormat.formatDuration(Duration.ofSeconds(PROCESSING_TIME_SECONDS), false)), PylonArgument.of("campfire", MULTIPLIER_CAMPFIRE), PylonArgument.of("soul_campfire", MULTIPLIER_SOUL_CAMPFIRE), PylonArgument.of("fire", MULTIPLIER_FIRE), @@ -215,7 +215,7 @@ private WailaDisplay getComponentWaila(@NotNull Player player) { "pylon.pylonbase.waila.pit_kiln.smelting", PylonArgument.of( "time", - UnitFormat.formatDuration(Duration.ofSeconds(processingTime.longValue())) + UnitFormat.formatDuration(Duration.ofSeconds(processingTime.longValue()), false) ) ) : Component.translatable("pylon.pylonbase.waila.pit_kiln.invalid_recipe"); diff --git a/src/main/resources/lang/en.yml b/src/main/resources/lang/en.yml index ed3467f5a..96fea1c56 100644 --- a/src/main/resources/lang/en.yml +++ b/src/main/resources/lang/en.yml @@ -368,11 +368,11 @@ item: fluid_meter: name: "Fluid Meter" lore: |- - A variant of the fluid filter which displays the amount of fluid passing through - Right click a fluid meter to change the filtered fluid + Measures and displays how much fluid is passing through + Right click a fluid meter to configure the measurement time Buffer: %buffer% - waila: "Fluid Meter | %bars% (%fluid%)" - gui: "Fluid Meter" + Measurement range: %min-measurement-time% to %max-measurement-time% + waila: "Fluid Meter | %bars% (%fluid%) | %duration%" fluid_strainer: name: "Fluid Strainer" @@ -1586,7 +1586,6 @@ item: name: "Cargo Buffer" lore: |- A one-stack item buffer - Connect using cargo ducts Transfer rate: %transfer-rate% cargo_extractor: @@ -1618,6 +1617,7 @@ item: name: "Cargo Valve" lore: |- Shift right click a cargo valve to toggle it + Transfer rate: %transfer-rate% waila: "Cargo Valve | %status%" cargo_filter: @@ -1625,6 +1625,45 @@ item: lore: |- Sends items matching the filter left Sends items that do not match the filter right + Transfer rate: %transfer-rate% + + cargo_monitor: + name: "Cargo Monitor" + lore: |- + Displays a hologram of its contents + Transfer rate: %transfer-rate% + + cargo_meter: + name: "Cargo Meter" + lore: |- + Measures and displays how many items are passing through + Measurement range: %min-measurement-time% to %max-measurement-time% + Transfer rate: %transfer-rate% + waila: "Cargo Meter | %duration%" + + cargo_gate: + name: "Cargo Gate" + lore: |- + Waits for a certain number of items to come from the left before sending on one item from the right + Transfer rate: %transfer-rate% + waila: "Cargo Gate | %threshold% | %side%" + + cargo_accumulator: + name: "Cargo Accumulator" + lore: |- + Waits for a certain number of items to arrive before sending them all onwards at once + New items cannot enter while there are still items in the output + Transfer rate: %transfer-rate% + waila: "Cargo Accumulator | %threshold%" + + cargo_fluid_accumulator: + name: "Cargo Fluid Accumulator" + lore: |- + Waits for a certain amount of items AND fluid to arrive before sending the items and fluid onwards all on at once + New items or fluid cannot enter while there are still items in the output + Transfer rate: %transfer-rate% + Fluid buffer: %fluid-buffer% + waila: "Cargo Fluid Accumulator | %item-threshold% | %fluid-threshold% | %bars%" hunger_talisman_simple: name: "Simple Hunger Talisman" @@ -2007,13 +2046,30 @@ gui: left_button: name: "Change ratio left" lore: |- - Left click to increase - Right click to decrease + Left click to increase + Right click to decrease right_button: name: "Change ratio right" lore: |- - Left click to increase - Right click to decrease + Left click to increase + Right click to decrease + threshold_button: + name: "Threshold: %threshold%" + lore: |- + Left click to increase + Right click to decrease + item_threshold_button: + name: "Item Threshold: %threshold%" + lore: |- + Left click to increase + Right click to decrease + fluid_threshold_button: + name: "Fluid Threshold: %threshold%" + lore: |- + Left click to increase by 10 + Right click to decrease by 10 + Shift left click to increase by 100 + Shift right click to decrease by 100 whitelist-blacklist-toggle: whitelist: name: "Whitelist mode" @@ -2059,6 +2115,13 @@ gui: name: "Current inventory: %inventory%" lore: |- Click to cycle + fluid_meter: + name: "Measurement duration: %measurement-duration%" + lore: |- + Left click to increase + Right click to decrease + Shift left click to increase (10x) + Shift right click to decrease (10x) inventory: fuel: "Fuel" diff --git a/src/main/resources/settings/cargo_accumulator.yml b/src/main/resources/settings/cargo_accumulator.yml new file mode 100644 index 000000000..540bfb074 --- /dev/null +++ b/src/main/resources/settings/cargo_accumulator.yml @@ -0,0 +1 @@ +transfer-rate: 1 \ No newline at end of file diff --git a/src/main/resources/settings/cargo_fluid_accumulator.yml b/src/main/resources/settings/cargo_fluid_accumulator.yml new file mode 100644 index 000000000..f2e8c3b3d --- /dev/null +++ b/src/main/resources/settings/cargo_fluid_accumulator.yml @@ -0,0 +1,2 @@ +fluid-buffer: 1000 +transfer-rate: 1 \ No newline at end of file diff --git a/src/main/resources/settings/cargo_gate.yml b/src/main/resources/settings/cargo_gate.yml new file mode 100644 index 000000000..540bfb074 --- /dev/null +++ b/src/main/resources/settings/cargo_gate.yml @@ -0,0 +1 @@ +transfer-rate: 1 \ No newline at end of file diff --git a/src/main/resources/settings/cargo_meter.yml b/src/main/resources/settings/cargo_meter.yml new file mode 100644 index 000000000..2f103da2b --- /dev/null +++ b/src/main/resources/settings/cargo_meter.yml @@ -0,0 +1,3 @@ +transfer-rate: 1 +min-number-of-measurements: 4 +max-number-of-measurements: 120 diff --git a/src/main/resources/settings/cargo_monitor.yml b/src/main/resources/settings/cargo_monitor.yml new file mode 100644 index 000000000..540bfb074 --- /dev/null +++ b/src/main/resources/settings/cargo_monitor.yml @@ -0,0 +1 @@ +transfer-rate: 1 \ No newline at end of file diff --git a/src/main/resources/settings/fluid_meter.yml b/src/main/resources/settings/fluid_meter.yml index a550fcef3..9532cf250 100644 --- a/src/main/resources/settings/fluid_meter.yml +++ b/src/main/resources/settings/fluid_meter.yml @@ -1,2 +1,3 @@ buffer: 1000 -tick-interval: 20 +min-number-of-measurements: 1 +max-number-of-measurements: 120 \ No newline at end of file