From 91622af5ce7c46d3c1031015ee2022e0900e6871 Mon Sep 17 00:00:00 2001 From: Face o' Cat Date: Thu, 5 Nov 2026 10:31:34 +0200 Subject: [PATCH 01/12] Add coloured inventory drawers and capability-driven automation addons --- docs/gui-screens.md | 4 + docs/inventory-automation.md | 118 +++ .../moddev/mc/companion/CompanionAddons.java | 163 ++++ .../moddev/mc/companion/CompanionMod.java | 143 +++- .../moddev/mc/companion/ProcessorEntity.java | 124 ++- .../client/CompanionWidgetsClient.java | 38 + .../blockstates/logistics.json | 7 + .../assets/mmdlib_companion/lang/en_us.json | 19 +- .../models/block/logistics.json | 6 + .../models/item/export_relay.json | 6 + .../models/item/fluid_filter.json | 6 + .../models/item/import_relay.json | 6 + .../models/item/input_pump.json | 6 + .../models/item/item_filter.json | 6 + .../models/item/large_item_filter.json | 6 + .../models/item/logistics.json | 3 + .../models/item/mechanical_puller.json | 6 + .../models/item/mechanical_pusher.json | 6 + .../models/item/output_pump.json | 6 + .../models/item/tag_filter.json | 6 + .../loot_table/blocks/logistics.json | 19 + .../resources/mmdlib/screen/crusher.xml | 4 +- .../resources/mmdlib/screen/dual_crusher.xml | 4 +- .../resources/mmdlib/screen/logistics.xml | 15 + .../resources/mmdlib/screen/transmuter.xml | 2 +- .../companion/test/AutomationClientSmoke.java | 108 +++ .../companion/test/AutomationGameTests.java | 363 ++++++++ .../companion/test/CompanionClientSmoke.java | 255 ++++-- .../lib/api/automation/AddonCapabilities.java | 19 + .../mc/lib/api/automation/AddonSpec.java | 29 + .../api/automation/AutomationInventory.java | 356 ++++++++ .../api/automation/AutomationTransfers.java | 268 ++++++ .../mc/lib/api/automation/FilterControl.java | 62 ++ .../lib/api/automation/FilterWidgetType.java | 51 ++ .../mc/lib/api/automation/InventoryAddon.java | 16 + .../lib/api/automation/InventoryFilter.java | 112 +++ .../mc/lib/api/automation/ResourceGroup.java | 140 ++++ .../mc/lib/api/automation/ResourceType.java | 53 ++ .../mc/lib/api/capabilities/FluidStorage.java | 62 +- .../mc/lib/api/capabilities/ItemStorage.java | 13 +- .../mc/lib/gui/client/AutomationDrawer.java | 774 ++++++++++++++++++ .../mc/lib/gui/client/FilterWidgets.java | 95 +++ .../mc/lib/gui/client/MachineScreen.java | 119 ++- .../mc/lib/gui/layout/FilterPanelLayout.java | 56 ++ .../mc/lib/gui/layout/ScreenLayout.java | 30 +- .../mc/lib/gui/runtime/AutomationMenu.java | 389 +++++++++ .../mc/lib/gui/runtime/AutomationNetwork.java | 152 ++++ .../mc/lib/gui/runtime/MachineMenu.java | 34 +- .../moddev/mc/lib/gui/runtime/ScreenHost.java | 13 +- .../mc/lib/gui/runtime/ScreenNetwork.java | 3 +- .../resources/assets/mmdlib/lang/en_us.json | 9 +- .../mc/lib/gui/FilterPanelLayoutTest.java | 56 ++ .../moddev/mc/lib/gui/ResourceGroupsTest.java | 71 ++ 53 files changed, 4236 insertions(+), 201 deletions(-) create mode 100644 docs/inventory-automation.md create mode 100644 src/companion/java/zone/moddev/mc/companion/CompanionAddons.java create mode 100644 src/companion/resources/assets/mmdlib_companion/blockstates/logistics.json create mode 100644 src/companion/resources/assets/mmdlib_companion/models/block/logistics.json create mode 100644 src/companion/resources/assets/mmdlib_companion/models/item/export_relay.json create mode 100644 src/companion/resources/assets/mmdlib_companion/models/item/fluid_filter.json create mode 100644 src/companion/resources/assets/mmdlib_companion/models/item/import_relay.json create mode 100644 src/companion/resources/assets/mmdlib_companion/models/item/input_pump.json create mode 100644 src/companion/resources/assets/mmdlib_companion/models/item/item_filter.json create mode 100644 src/companion/resources/assets/mmdlib_companion/models/item/large_item_filter.json create mode 100644 src/companion/resources/assets/mmdlib_companion/models/item/logistics.json create mode 100644 src/companion/resources/assets/mmdlib_companion/models/item/mechanical_puller.json create mode 100644 src/companion/resources/assets/mmdlib_companion/models/item/mechanical_pusher.json create mode 100644 src/companion/resources/assets/mmdlib_companion/models/item/output_pump.json create mode 100644 src/companion/resources/assets/mmdlib_companion/models/item/tag_filter.json create mode 100644 src/companion/resources/data/mmdlib_companion/loot_table/blocks/logistics.json create mode 100644 src/companion/resources/mmdlib/screen/logistics.xml create mode 100644 src/companionTest/java/zone/moddev/mc/companion/test/AutomationClientSmoke.java create mode 100644 src/companionTest/java/zone/moddev/mc/companion/test/AutomationGameTests.java create mode 100644 src/main/java/zone/moddev/mc/lib/api/automation/AddonCapabilities.java create mode 100644 src/main/java/zone/moddev/mc/lib/api/automation/AddonSpec.java create mode 100644 src/main/java/zone/moddev/mc/lib/api/automation/AutomationInventory.java create mode 100644 src/main/java/zone/moddev/mc/lib/api/automation/AutomationTransfers.java create mode 100644 src/main/java/zone/moddev/mc/lib/api/automation/FilterControl.java create mode 100644 src/main/java/zone/moddev/mc/lib/api/automation/FilterWidgetType.java create mode 100644 src/main/java/zone/moddev/mc/lib/api/automation/InventoryAddon.java create mode 100644 src/main/java/zone/moddev/mc/lib/api/automation/InventoryFilter.java create mode 100644 src/main/java/zone/moddev/mc/lib/api/automation/ResourceGroup.java create mode 100644 src/main/java/zone/moddev/mc/lib/api/automation/ResourceType.java create mode 100644 src/main/java/zone/moddev/mc/lib/gui/client/AutomationDrawer.java create mode 100644 src/main/java/zone/moddev/mc/lib/gui/client/FilterWidgets.java create mode 100644 src/main/java/zone/moddev/mc/lib/gui/layout/FilterPanelLayout.java create mode 100644 src/main/java/zone/moddev/mc/lib/gui/runtime/AutomationMenu.java create mode 100644 src/main/java/zone/moddev/mc/lib/gui/runtime/AutomationNetwork.java create mode 100644 src/test/java/zone/moddev/mc/lib/gui/FilterPanelLayoutTest.java create mode 100644 src/test/java/zone/moddev/mc/lib/gui/ResourceGroupsTest.java diff --git a/docs/gui-screens.md b/docs/gui-screens.md index 2e3fca9e..9a20b8f5 100644 --- a/docs/gui-screens.md +++ b/docs/gui-screens.md @@ -4,6 +4,10 @@ > open through `block-right-click`, and use one generic menu with custom value packets. > Rendering and optional JEI integration live exclusively on the client. +Machines can opt into [inventory colours, side drawers and item-provided filter editors](inventory-automation.md) +through `ScreenHost.screenAutomation()`. This adds the selectors, fixed addon/filter slots and +embedded editor without adding per-filter XML to each screen. + ## Implementation map | Piece | Where | State | diff --git a/docs/inventory-automation.md b/docs/inventory-automation.md new file mode 100644 index 00000000..32abae37 --- /dev/null +++ b/docs/inventory-automation.md @@ -0,0 +1,118 @@ +# Inventory colours, sides and addons + +Implement `ScreenHost.screenAutomation()` to opt a machine into the inventory drawer. Existing +screens need no new XML element. The host inserts a colour selector row above the player inventory, +reserves the first right drawer button, and places a second selector vertically inside the drawer. +Each selection configures one resource group. F6 focuses the selector; arrow keys change inventory. + +## Define independent resource groups + +A `ResourceGroup` declares a stable save ID, translated name, RGB colour, `ResourceType`, +supported `IoMode`, member indices, optional screen element IDs to highlight, and `SideConfig`. +Bind its facing supplier and capability view, then create one persistent `AutomationInventory` +containing the groups. Use the same definitions on client and server. + +```java +var input = new ResourceGroup<>("input", Component.translatable("example.input"), + 0x568bd8, ResourceType.ITEMS, IoMode.INPUT, List.of(0, 1), List.of("input_slots"), + new SideConfig(IoMode.INPUT)); +input.bind(this::facing, side -> items.view(slot -> + input.contains(slot) ? input.mode(side) : IoMode.DISABLED)); +automation = new AutomationInventory(this, List.of(input)); +``` + +Register passive item/fluid capabilities using `storage.view(index -> automation.mode(type,index,side))`. +Use `energy.view(() -> automation.mode(ResourceType.ENERGY,0,side))` for energy. Every view retains +all original slot/tank indices; permissions refuse operations instead of renumbering storage. +The group's **active transfer port must restrict access to that group alone**, as above. +Both views preserve storage reservations. Null-side views still honour the declared group role. + +Groups are saved by ID, independently of UI ordering. Call `automation.save(registries)` and +`automation.load(tag, registries)` in the block entity's persistence hooks. Missing groups preserve +their raw saved content. Call `automation.dropAll()` from the block's contents-drop hook. Item +transfer remainders are dropped too; fluid and energy buffers follow the usual machine-break +semantics and are discarded. Hosts that retain fluids/energy on break should preserve the saved +automation data alongside their other storage. Do not also drop the same addon slots separately. + +## Addon item capabilities + +Register `AddonCapabilities.ADDON` for any item through `RegisterCapabilitiesEvent`. Its +`InventoryAddon` returns an `AddonSpec`: stable type ID, resource type, required operations, +batch size, interval, RF cost per unit and filter support. Acceptance requires **both** the +resource type and supported inventory operations. Disabling every face does not invalidate an addon. + +Each group has four addon slots. A filter-capable addon exposes a smaller child slot. Register +`AddonCapabilities.FILTER` for filter items; `acceptsFilter` can further restrict compatible filters. +The parent and child are real fixed menu slots. Ghost entries in editors are not real item slots. +All vanilla interaction paths use the same slot validation, including shift-click and hotbar swap. +Removing/replacing a parent returns its configured filter to the acting player's inventory, or +drops it beside the machine when full. Programmatic removals queue recovery; call `recover(player)` +after the operation, or `recover(null)` to drop queued items. Recovery is persisted across saves. + +Unknown or incompatible installed items remain removable. An installed filter whose capability is +missing blocks its module; it does not become an unfiltered transfer. Empty optional filter slots +are intentionally unfiltered. + +## Item-provided filter panels + +`InventoryFilter` supplies a resource type, editor ID, control tree, matching predicate and +configuration validation. The default configuration adapter stores only the `mmdlib_filter` +compound in the stack's custom data and preserves unrelated components. + +```java +public FilterControl controls(ItemStack stack) { + return new FilterControl.Box("panel", false, List.of( + new FilterControl.Toggle("deny", MY_ICON, Component.translatable("example.deny")), + new FilterControl.Samples("entries", 18, ResourceType.ITEMS))); +} +``` + +Use nested `Box` controls for rows/columns, `Toggle` for icon buttons, `Text` for bounded text/tag +input, `Number` for bounded numeric values, and `Samples` for a wrapped ghost grid. Width is supplied +by the drawer; tall content scrolls within the available screen space. Counts, nesting, IDs and +text lengths are bounded. A sample comes from the **server's cursor stack**, without consuming it. +Standard item samples match item IDs; fluid samples use a fluid container's contents. A filter +may override sample extraction and validation for stricter matching. + +For specialised controls, register a common `FilterWidgetType` with a bounded action codec and +a handler that updates only a candidate configuration. Include `FilterControl.Custom` in the tree. +On the client, `FilterWidgets.register(type, factory)` supplies a fresh instance per open panel. +Its access object exposes measured bounds, configuration copies, pending state and a typed +submitter. The host clips rendering and routes focus, keyboard, pointer dragging, release and +scroll events. `cancel`/`removed` let controls discard drafts and capture when their attachment, +layout or selection changes. Client registrations must stay in client-only classes. + +The server resolves the current installed item and declared control before any edit. Menu session, +attachment generation and configuration revision reject stale/replayed edits and concurrent +overwrites. The candidate is committed only after full filter validation. Authoritative stack sync +updates all viewers. Return `false` from validation to keep the original configuration unchanged. +Custom action JSON is limited to 256 characters; choose codecs with bounded collections and values. + +See `CompanionAddons` and `CompanionWidgetsClient`: the basic/expanded item filters use different +grid sizes, the fluid filter accepts fluid samples, and the tag filter supplies a text field plus +a separately registered custom allow/deny button. None requires machine-specific screen XML. + +## Active transfers + +Call `AutomationTransfers.tick(automation, energy)` once per server tick, after recipe processing. +The scheduler rotates module and face priorities, observes enabled sides and installed predicates, +and charges RF only for actual delivery. It never force-loads a neighbour's chunk. Built-in item, +fluid and energy transports simulate before extracting and retain unexpected insertion remainders +in persistent escrow for later delivery. Escrow retains its original direction, face and cost even +if the module is removed; disabling that face pauses its completion. It cannot be silently reused +as a new transfer with different settings. Capability implementations must obey the normal +simulation/count contracts; no API can recover reliably from an external handler throwing after +an undocumented partial mutation. + +The companion uses paid mechanical pullers/pushers and fluid pumps. Its energy relays are lossless +and have no auxiliary RF fee, so importing can bootstrap an empty buffer. Paid transports should +use a power buffer separate from the resource being moved to avoid sharing refund capacity. + +Register future content with `ResourceType.register(id, capabilityInterface)` and +`AutomationTransfers.register(type, transport)`. Custom transports own their bounded persistent +escrow schema and must preserve unaccepted resources. Resource types are not a closed enum; gas +support can add a capability and transport without changing the drawer. + +The optional companion's Logistics Workbench demonstrates seven groups: two item inventories, +four independent fluid tanks and energy. Its output tanks can be filled by the owning machine/test +setup; passive external insertion correctly refuses them. diff --git a/src/companion/java/zone/moddev/mc/companion/CompanionAddons.java b/src/companion/java/zone/moddev/mc/companion/CompanionAddons.java new file mode 100644 index 00000000..eb364e0f --- /dev/null +++ b/src/companion/java/zone/moddev/mc/companion/CompanionAddons.java @@ -0,0 +1,163 @@ +package zone.moddev.mc.companion; + +import java.util.*; + +import net.minecraft.core.HolderLookup; +import net.minecraft.core.registries.*; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.*; + +import net.neoforged.bus.api.IEventBus; +import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent; +import net.neoforged.neoforge.fluids.FluidStack; +import net.neoforged.neoforge.registries.*; + +import zone.moddev.mc.lib.api.automation.*; +import zone.moddev.mc.lib.api.capabilities.IoMode; + +/** Companion content demonstrates capabilities: the library does not register concrete addons. */ +public final class CompanionAddons { + private static final DeferredRegister ITEMS = + DeferredRegister.create(Registries.ITEM, CompanionMod.ID); + private static final Map, InventoryAddon> ADDONS = + new LinkedHashMap<>(); + private static final Map, InventoryFilter> FILTERS = + new LinkedHashMap<>(); + public static final FilterWidgetType TAG_MODE = + FilterWidgetType.register( + CompanionMod.id("tag_mode"), + com.mojang.serialization.Codec.BOOL, + (config, deny) -> { + config.putBoolean("deny", deny); + return true; + }); + public static final DeferredHolder PULLER = + addon("mechanical_puller", ResourceType.ITEMS, IoMode.INPUT, 8, 20, 4, true); + public static final DeferredHolder PUSHER = + addon("mechanical_pusher", ResourceType.ITEMS, IoMode.OUTPUT, 8, 20, 4, true); + public static final DeferredHolder INPUT_PUMP = + addon("input_pump", ResourceType.FLUIDS, IoMode.INPUT, 250, 10, 1, true); + public static final DeferredHolder OUTPUT_PUMP = + addon("output_pump", ResourceType.FLUIDS, IoMode.OUTPUT, 250, 10, 1, true); + public static final DeferredHolder IMPORT_RELAY = + addon("import_relay", ResourceType.ENERGY, IoMode.INPUT, 1000, 5, 0, false); + public static final DeferredHolder EXPORT_RELAY = + addon("export_relay", ResourceType.ENERGY, IoMode.OUTPUT, 1000, 5, 0, false); + public static final DeferredHolder ITEM_FILTER = + filter("item_filter", ResourceType.ITEMS, 9, false); + public static final DeferredHolder LARGE_FILTER = + filter("large_item_filter", ResourceType.ITEMS, 36, false); + public static final DeferredHolder TAG_FILTER = + filter("tag_filter", ResourceType.ITEMS, 0, true); + public static final DeferredHolder FLUID_FILTER = + filter("fluid_filter", ResourceType.FLUIDS, 6, false); + + private static DeferredHolder addon( + String name, + ResourceType type, + IoMode direction, + int batch, + int interval, + long cost, + boolean filters) { + var item = ITEMS.register(name, () -> new Item(new Item.Properties().stacksTo(1))); + var spec = + new AddonSpec(CompanionMod.id(name), type, direction, batch, interval, cost, filters); + ADDONS.put(item, stack -> spec); + return item; + } + + private static DeferredHolder filter( + String name, ResourceType type, int size, boolean tag) { + var item = ITEMS.register(name, () -> new Item(new Item.Properties().stacksTo(1))); + FILTERS.put( + item, + new InventoryFilter() { + public ResourceType resource(ItemStack stack) { + return type; + } + + public ResourceLocation editorType(ItemStack stack) { + return CompanionMod.id(name); + } + + public FilterControl controls(ItemStack stack) { + return new FilterControl.Box( + "root", + false, + List.of( + tag + ? new FilterControl.Custom("deny", TAG_MODE, 14) + : new FilterControl.Toggle( + "deny", + ResourceLocation.parse("mmdlib:icon_sides"), + Component.translatable("screen.mmdlib_companion.filter.deny")), + tag + ? new FilterControl.Text( + "tag", + 128, + Component.translatable("screen.mmdlib_companion.filter.tag")) + : new FilterControl.Samples("samples", size, type))); + } + + public boolean validate( + ItemStack stack, + net.minecraft.nbt.CompoundTag config, + HolderLookup.Provider registries) { + if (!tag || config.getString("tag").isEmpty()) return true; + var id = ResourceLocation.tryParse(config.getString("tag")); + return id != null + && registries + .lookupOrThrow(Registries.ITEM) + .get(TagKey.create(Registries.ITEM, id)) + .isPresent(); + } + + public boolean matches( + ItemStack stack, Object candidate, HolderLookup.Provider registries) { + var config = configuration(stack); + boolean match = false; + if (tag && candidate instanceof ItemStack item) { + var id = ResourceLocation.tryParse(config.getString("tag")); + match = id != null && item.is(TagKey.create(Registries.ITEM, id)); + } else { + String id = + candidate instanceof ItemStack item + ? BuiltInRegistries.ITEM.getKey(item.getItem()).toString() + : candidate instanceof FluidStack fluid + ? BuiltInRegistries.FLUID.getKey(fluid.getFluid()).toString() + : ""; + var samples = config.getList("samples", net.minecraft.nbt.Tag.TAG_STRING); + for (int i = 0; i < samples.size(); i++) + if (samples.getString(i).equals(id)) { + match = true; + break; + } + } + return config.getBoolean("deny") ? !match : match; + } + }); + return item; + } + + public static List> items() { + var result = new ArrayList<>(ADDONS.keySet()); + result.addAll(FILTERS.keySet()); + return List.copyOf(result); + } + + public static void register(IEventBus bus) { + ITEMS.register(bus); + } + + public static void capabilities(RegisterCapabilitiesEvent event) { + ADDONS.forEach( + (item, provider) -> + event.registerItem(AddonCapabilities.ADDON, (stack, context) -> provider, item.get())); + FILTERS.forEach( + (item, provider) -> + event.registerItem(AddonCapabilities.FILTER, (stack, context) -> provider, item.get())); + } +} diff --git a/src/companion/java/zone/moddev/mc/companion/CompanionMod.java b/src/companion/java/zone/moddev/mc/companion/CompanionMod.java index 9c798c46..68d9d3af 100644 --- a/src/companion/java/zone/moddev/mc/companion/CompanionMod.java +++ b/src/companion/java/zone/moddev/mc/companion/CompanionMod.java @@ -37,7 +37,8 @@ @Mod(CompanionMod.ID) public final class CompanionMod { public static final String ID = "mmdlib_companion"; - private static final DeferredRegister BLOCKS = DeferredRegister.create(Registries.BLOCK, ID); + private static final DeferredRegister BLOCKS = + DeferredRegister.create(Registries.BLOCK, ID); private static final DeferredRegister ITEMS = DeferredRegister.create(Registries.ITEM, ID); private static final DeferredRegister> ENTITIES = DeferredRegister.create(Registries.BLOCK_ENTITY_TYPE, ID); @@ -53,23 +54,32 @@ public final class CompanionMod { public static final MachineRecipeType TRANSMUTING = MachineRecipeTypes.register(RECIPE_TYPES, SERIALIZERS, "transmuting"); public static final Machine CRUSHER = new Machine("crusher", ProcessorEntity.Kind.CRUSHER); - public static final Machine TRANSMUTER = new Machine("transmuter", ProcessorEntity.Kind.TRANSMUTER); + public static final Machine TRANSMUTER = + new Machine("transmuter", ProcessorEntity.Kind.TRANSMUTER); public static final Machine DUAL_CRUSHER = new Machine("dual_crusher", ProcessorEntity.Kind.DUAL); public static final Machine CREATIVE_POWER = new Machine("creative_power", null); + public static final Machine LOGISTICS = new Machine("logistics", ProcessorEntity.Kind.LOGISTICS); public static final List MACHINES = - List.of(CRUSHER, TRANSMUTER, DUAL_CRUSHER, CREATIVE_POWER); + List.of(CRUSHER, TRANSMUTER, DUAL_CRUSHER, CREATIVE_POWER, LOGISTICS); static { - TABS.register("machines", () -> CreativeModeTab.builder() - .title(Component.translatable("itemGroup.mmdlib_companion")) - .icon(() -> new ItemStack(CRUSHER.block.get())) - .displayItems((parameters, output) -> - MACHINES.forEach(machine -> output.accept(machine.block.get()))) - .build()); + TABS.register( + "machines", + () -> + CreativeModeTab.builder() + .title(Component.translatable("itemGroup.mmdlib_companion")) + .icon(() -> new ItemStack(CRUSHER.block.get())) + .displayItems( + (parameters, output) -> { + MACHINES.forEach(machine -> output.accept(machine.block.get())); + CompanionAddons.items().forEach(item -> output.accept(item.get())); + }) + .build()); } public CompanionMod(IEventBus bus) { CompanionWidgets.register(); + CompanionAddons.register(bus); BLOCKS.register(bus); ITEMS.register(bus); ENTITIES.register(bus); @@ -77,50 +87,101 @@ public CompanionMod(IEventBus bus) { SERIALIZERS.register(bus); TABS.register(bus); bus.addListener(CompanionMod::capabilities); - MaterialExtensions.GLOBAL.events().registerCallback( - id("tank_container"), MaterialEventTypes.GUI_CLICK, CompanionMod::transferTankContainer); - MaterialExtensions.GLOBAL.events().registerCallback( - id("redstone"), MaterialEventTypes.GUI_CLICK, - click -> { - if (click.menu().blockEntity instanceof ProcessorEntity machine) machine.cycleRedstoneMode(); - }); + MaterialExtensions.GLOBAL + .events() + .registerCallback( + id("tank_container"), + MaterialEventTypes.GUI_CLICK, + CompanionMod::transferTankContainer); + MaterialExtensions.GLOBAL + .events() + .registerCallback( + id("redstone"), + MaterialEventTypes.GUI_CLICK, + click -> { + if (click.menu().blockEntity instanceof ProcessorEntity machine) + machine.cycleRedstoneMode(); + }); for (int lane = 0; lane < 2; lane++) { int index = lane; - MaterialExtensions.GLOBAL.events().registerCallback( - id("toggle_" + lane), MaterialEventTypes.GUI_CLICK, - click -> { - if (click.menu().blockEntity instanceof ProcessorEntity machine) machine.toggle(index); - }); + MaterialExtensions.GLOBAL + .events() + .registerCallback( + id("toggle_" + lane), + MaterialEventTypes.GUI_CLICK, + click -> { + if (click.menu().blockEntity instanceof ProcessorEntity machine) + machine.toggle(index); + }); } } private static void transferTankContainer(ScreenClick click) { - if (!(click.menu().blockEntity instanceof ProcessorEntity machine) || !machine.hasTank()) return; + if (!(click.menu().blockEntity instanceof ProcessorEntity machine) || !machine.hasTank()) + return; var carried = click.menu().getCarried(); if (carried.isEmpty() || FluidUtil.getFluidHandler(carried).isEmpty()) return; var inventory = click.player().getCapability(Capabilities.ItemHandler.ENTITY); if (inventory == null) return; // Work on the server's cursor stack. NeoForge handles partial containers and stacked results. - var result = FluidUtil.tryEmptyContainerAndStow( - carried, machine.fluids, inventory, Integer.MAX_VALUE, click.player(), true); + var result = + FluidUtil.tryEmptyContainerAndStow( + carried, machine.fluids, inventory, Integer.MAX_VALUE, click.player(), true); if (!result.isSuccess()) - result = FluidUtil.tryFillContainerAndStow( - carried, machine.fluids, inventory, Integer.MAX_VALUE, click.player(), true); + result = + FluidUtil.tryFillContainerAndStow( + carried, machine.fluids, inventory, Integer.MAX_VALUE, click.player(), true); if (result.isSuccess()) { click.menu().setCarried(result.getResult()); click.menu().broadcastChanges(); } else { - click.player().displayClientMessage(Component.translatable("screen.mmdlib_companion.tank.refused"), true); + click + .player() + .displayClientMessage( + Component.translatable("screen.mmdlib_companion.tank.refused"), true); } } private static void capabilities(RegisterCapabilitiesEvent event) { - MachineCapabilities.fluids(event, CRUSHER.type.get(), entity -> ((ProcessorEntity) entity).fluids); + CompanionAddons.capabilities(event); for (var machine : MACHINES) { - MachineCapabilities.energy(event, machine.type.get(), entity -> entity.energy, - (entity, side) -> entity instanceof CreativePowerEntity ? IoMode.OUTPUT : IoMode.INPUT); - if (machine != CREATIVE_POWER) - MachineCapabilities.items(event, machine.type.get(), entity -> ((ProcessorEntity) entity).items); + if (machine == CREATIVE_POWER) { + MachineCapabilities.energy( + event, machine.type.get(), entity -> entity.energy, (entity, side) -> IoMode.OUTPUT); + continue; + } + event.registerBlockEntity( + Capabilities.ItemHandler.BLOCK, + machine.type.get(), + (entity, side) -> { + var p = (ProcessorEntity) entity; + return p.items.view( + slot -> + p.automation.mode( + zone.moddev.mc.lib.api.automation.ResourceType.ITEMS, slot, side)); + }); + event.registerBlockEntity( + Capabilities.FluidHandler.BLOCK, + machine.type.get(), + (entity, side) -> { + var p = (ProcessorEntity) entity; + return p.hasTank() + ? p.fluids.view( + tank -> + p.automation.mode( + zone.moddev.mc.lib.api.automation.ResourceType.FLUIDS, tank, side)) + : null; + }); + event.registerBlockEntity( + Capabilities.EnergyStorage.BLOCK, + machine.type.get(), + (entity, side) -> { + var p = (ProcessorEntity) entity; + return p.energy.view( + () -> + p.automation.mode( + zone.moddev.mc.lib.api.automation.ResourceType.ENERGY, 0, side)); + }); } } @@ -136,11 +197,16 @@ public static final class Machine { private Machine(String name, ProcessorEntity.Kind kind) { this.kind = kind; - block = BLOCKS.register(name, () -> new DemoBlock( - BlockBehaviour.Properties.of().strength(3.5F).requiresCorrectToolForDrops(), - this::entityType)); - type = ENTITIES.register(name, () -> - BlockEntityType.Builder.of(this::create, block.get()).build(null)); + block = + BLOCKS.register( + name, + () -> + new DemoBlock( + BlockBehaviour.Properties.of().strength(3.5F).requiresCorrectToolForDrops(), + this::entityType)); + type = + ENTITIES.register( + name, () -> BlockEntityType.Builder.of(this::create, block.get()).build(null)); ITEMS.register(name, () -> new BlockItem(block.get(), new Item.Properties())); } @@ -149,7 +215,8 @@ private BlockEntityType entityType() { } private DemoEntity create(BlockPos pos, BlockState state) { - return kind == null ? new CreativePowerEntity(type.get(), pos, state) + return kind == null + ? new CreativePowerEntity(type.get(), pos, state) : new ProcessorEntity(type.get(), pos, state, kind); } } diff --git a/src/companion/java/zone/moddev/mc/companion/ProcessorEntity.java b/src/companion/java/zone/moddev/mc/companion/ProcessorEntity.java index 4d18816d..610d4018 100644 --- a/src/companion/java/zone/moddev/mc/companion/ProcessorEntity.java +++ b/src/companion/java/zone/moddev/mc/companion/ProcessorEntity.java @@ -4,6 +4,7 @@ import java.util.List; import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; import net.minecraft.core.HolderLookup; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtOps; @@ -21,6 +22,8 @@ import net.neoforged.neoforge.fluids.FluidStack; +import zone.moddev.mc.lib.api.automation.*; +import zone.moddev.mc.lib.api.capabilities.*; import zone.moddev.mc.lib.api.capabilities.FluidStorage; import zone.moddev.mc.lib.api.capabilities.ItemStorage; import zone.moddev.mc.lib.api.capabilities.Role; @@ -34,17 +37,21 @@ import zone.moddev.mc.lib.machine.neoforge.StorageGroups; /** The dual crusher deliberately runs two lanes over the same input and output groups. */ -public final class ProcessorEntity extends DemoEntity implements ScreenHost, DropsContents, Clearable { +public final class ProcessorEntity extends DemoEntity + implements ScreenHost, DropsContents, Clearable { public enum Kind { CRUSHER, TRANSMUTER, - DUAL + DUAL, + LOGISTICS } public final ItemStorage items; + public final AutomationInventory automation; public final FluidStorage fluids = new FluidStorage().onChange(this::setChanged); public final List lanes; - public final FeatureHost features = new FeatureHost(this::setChanged, () -> FeatureSync.send(this)); + public final FeatureHost features = + new FeatureHost(this::setChanged, () -> FeatureSync.send(this)); private final Kind kind; // 0 ignores redstone, 1 requires a signal, 2 requires no signal. Manual pause is separate. private int redstoneMode; @@ -78,16 +85,22 @@ public ProcessorEntity(BlockEntityType type, BlockPos pos, BlockState state, super(type, pos, state); this.kind = kind; if (kind == Kind.CRUSHER) - fluids.addTank(4_000, Role.STORAGE, + fluids.addTank( + 4_000, + Role.STORAGE, fluid -> fluid.getFluid() == Fluids.WATER || fluid.getFluid() == Fluids.LAVA); + if (kind == Kind.LOGISTICS) + for (int i = 0; i < 4; i++) fluids.addTank(4000, i < 2 ? Role.INPUT : Role.OUTPUT); var inventory = MachineInventory.builder().group("energy", StorageGroups.energy(energy)); if (kind == Kind.TRANSMUTER) { // One key, and one item: the recipe consumes the whole slot before inserting gold. items = new ItemStorage(1).limit(1, 0).onChange(this::setChanged); inventory.group("slot", StorageGroups.items(items, 0)); } else { - items = new ItemStorage(2).role(Role.INPUT, 0).role(Role.OUTPUT, 1).onChange(this::setChanged); - inventory.group("input", StorageGroups.items(items, 0)) + items = + new ItemStorage(2).role(Role.INPUT, 0).role(Role.OUTPUT, 1).onChange(this::setChanged); + inventory + .group("input", StorageGroups.items(items, 0)) .group("output", StorageGroups.items(items, 1)); } var shared = inventory.build(); @@ -99,12 +112,85 @@ public ProcessorEntity(BlockEntityType type, BlockPos pos, BlockState state, features.add(processor); } lanes = List.copyOf(processors); + var groups = new ArrayList>(); + groups.add( + itemGroup("input", 0, 0x568bd8, kind == Kind.TRANSMUTER ? IoMode.BOTH : IoMode.INPUT)); + if (kind != Kind.TRANSMUTER) groups.add(itemGroup("output", 1, 0xe48f45, IoMode.OUTPUT)); + int[] colours = {0x64bfc1, 0xa678cb, 0xe0c55d, 0xce668e}; + for (int i = 0; i < fluids.getTanks(); i++) { + final int tank = i; + var group = + new ResourceGroup<>( + "tank_" + i, + Component.translatable("screen.mmdlib_companion.group.tank", i + 1), + colours[i], + ResourceType.FLUIDS, + kind == Kind.CRUSHER ? IoMode.BOTH : (i < 2 ? IoMode.INPUT : IoMode.OUTPUT), + List.of(i), + List.of(i == 0 ? "tank" : "tank_" + i), + new SideConfig( + kind == Kind.CRUSHER ? IoMode.BOTH : (i < 2 ? IoMode.INPUT : IoMode.OUTPUT))); + group.bind( + () -> Direction.NORTH, + side -> fluids.view(index -> index == tank ? group.mode(side) : IoMode.DISABLED)); + groups.add(group); + } + var energyGroup = + new ResourceGroup<>( + "energy", + Component.translatable("screen.mmdlib_companion.group.energy"), + 0xd85b5b, + ResourceType.ENERGY, + kind == Kind.LOGISTICS ? IoMode.BOTH : IoMode.INPUT, + List.of(0), + List.of("energy"), + new SideConfig(kind == Kind.LOGISTICS ? IoMode.BOTH : IoMode.INPUT)); + if (kind == Kind.LOGISTICS) energy.setRates(1000, 1000); + energyGroup.bind(() -> Direction.NORTH, side -> energy.view(() -> energyGroup.mode(side))); + groups.add(energyGroup); + automation = new AutomationInventory(this, groups); + } + + private ResourceGroup itemGroup( + String id, int slot, int colour, IoMode mode) { + var group = + new ResourceGroup<>( + id, + Component.translatable("screen.mmdlib_companion.group." + id), + colour, + ResourceType.ITEMS, + mode, + List.of(slot), + List.of(id), + new SideConfig(mode)); + return group.bind( + () -> Direction.NORTH, + side -> items.view(index -> index == slot ? group.mode(side) : IoMode.DISABLED)); + } + + @Override + public AutomationInventory screenAutomation() { + return automation; } @Override public void serverTick() { - if (redstoneAllowsRun() && energy.stored() >= energy.capacity() * reservePercent / 100) - features.tick(level); + if (kind != Kind.LOGISTICS + && redstoneAllowsRun() + && energy.stored() >= energy.capacity() * reservePercent / 100) features.tick(level); + AutomationTransfers.tick(automation, energy); + } + + public FluidStack getTank1() { + return fluids.getFluidInTank(1); + } + + public FluidStack getTank2() { + return fluids.getFluidInTank(2); + } + + public FluidStack getTank3() { + return fluids.getFluidInTank(3); } public int getRedstoneMode() { @@ -117,8 +203,7 @@ public void cycleRedstoneMode() { } private boolean redstoneAllowsRun() { - return redstoneMode == 0 - || (level.hasNeighborSignal(worldPosition) == (redstoneMode == 1)); + return redstoneMode == 0 || (level.hasNeighborSignal(worldPosition) == (redstoneMode == 1)); } public void toggle(int index) { @@ -177,9 +262,13 @@ public List getWarnings() { for (int i = 0; i < lanes.size(); i++) { var status = lanes.get(i).status(); if (status == RecipeProcessor.Status.STALLED || status == RecipeProcessor.Status.BLOCKED) - warnings.add(Component.translatable("screen.mmdlib_companion.lane_" + (i == 0 ? "a" : "b")) - .append(": ").append(Component.translatable( - "status.mmdlib_companion." + status.name().toLowerCase(java.util.Locale.ROOT)))); + warnings.add( + Component.translatable("screen.mmdlib_companion.lane_" + (i == 0 ? "a" : "b")) + .append(": ") + .append( + Component.translatable( + "status.mmdlib_companion." + + status.name().toLowerCase(java.util.Locale.ROOT)))); } return warnings; } @@ -198,17 +287,19 @@ public int[] screenRecipeSlots(ResourceLocation category) { @Override public void dropContents(Level level, BlockPos pos) { items.dropAll(level, pos); + automation.dropAll(); } @Override public void clearContent() { + automation.clear(); lanes.forEach(RecipeProcessor::reset); for (int slot = 0; slot < items.getSlots(); slot++) { items.setPlan(slot, SlotPlan.NONE); items.setStackInSlot(slot, ItemStack.EMPTY); } energy.setStored(0); - if (hasTank()) fluids.tank(0).setFluid(FluidStack.EMPTY); + for (int i = 0; i < fluids.getTanks(); i++) fluids.tank(i).setFluid(FluidStack.EMPTY); setChanged(); FeatureSync.send(this); } @@ -217,6 +308,7 @@ public void clearContent() { protected void saveAdditional(CompoundTag tag, HolderLookup.Provider registries) { super.saveAdditional(tag, registries); tag.put("items", items.serializeNBT(registries)); + tag.put("automation", automation.save(registries)); tag.putInt("redstone_mode", redstoneMode); tag.putString("machine_name", machineName); tag.putInt("reserve_percent", reservePercent); @@ -239,7 +331,9 @@ protected void loadAdditional(CompoundTag tag, HolderLookup.Provider registries) : "Crusher"; reservePercent = Math.clamp(tag.getInt("reserve_percent"), 0, 100); items.deserializeNBT(registries, tag.getCompound("items")); - if (hasTank() && tag.contains("fluids")) fluids.deserializeNBT(registries, tag.getCompound("fluids")); + automation.load(tag.getCompound("automation"), registries); + if (hasTank() && tag.contains("fluids")) + fluids.deserializeNBT(registries, tag.getCompound("fluids")); features.load(registries.createSerializationContext(NbtOps.INSTANCE), tag.get("features")); } diff --git a/src/companion/java/zone/moddev/mc/companion/client/CompanionWidgetsClient.java b/src/companion/java/zone/moddev/mc/companion/client/CompanionWidgetsClient.java index 0212392c..9af95658 100644 --- a/src/companion/java/zone/moddev/mc/companion/client/CompanionWidgetsClient.java +++ b/src/companion/java/zone/moddev/mc/companion/client/CompanionWidgetsClient.java @@ -17,6 +17,44 @@ private CompanionWidgetsClient() {} @SubscribeEvent public static void setup(FMLClientSetupEvent event) { + zone.moddev.mc.lib.gui.client.FilterWidgets.register( + zone.moddev.mc.companion.CompanionAddons.TAG_MODE, + access -> + new zone.moddev.mc.lib.gui.client.FilterWidgets.Instance() { + public void render( + net.minecraft.client.gui.GuiGraphics g, + zone.moddev.mc.lib.gui.layout.Rect r, + net.minecraft.nbt.CompoundTag config, + int mx, + int my) { + g.fill( + r.x(), + r.y(), + r.x() + 14, + r.y() + 14, + config.getBoolean("deny") ? 0xffb95555 : 0xff55a776); + g.renderOutline(r.x(), r.y(), 14, 14, 0xff263b50); + g.fill(r.x() + 3, r.y() + 6, r.x() + 11, r.y() + 8, 0xffffffff); + if (!config.getBoolean("deny")) + g.fill(r.x() + 6, r.y() + 3, r.x() + 8, r.y() + 11, 0xffffffff); + } + + public net.minecraft.network.chat.Component tooltip() { + return net.minecraft.network.chat.Component.translatable( + "screen.mmdlib_companion.filter.deny"); + } + + public boolean click(double x, double y, int button) { + if (!access.pending().getAsBoolean()) + access.submit().accept(!access.configuration().get().getBoolean("deny")); + return true; + } + + public boolean key(int key, int scan, int modifiers) { + if (key == 257 || key == 32) return click(0, 0, 0); + return false; + } + }); WidgetRenderers.GLOBAL.registerInteractive(CompanionWidgets.NAME_EDITOR, NameEditor::new); WidgetRenderers.GLOBAL.registerInteractive(CompanionWidgets.RESERVE_SLIDER, ReserveSlider::new); WidgetRenderers.GLOBAL.registerInteractive(CompanionWidgets.LANE_SELECTOR, LaneSelector::new); diff --git a/src/companion/resources/assets/mmdlib_companion/blockstates/logistics.json b/src/companion/resources/assets/mmdlib_companion/blockstates/logistics.json new file mode 100644 index 00000000..5d17bc0f --- /dev/null +++ b/src/companion/resources/assets/mmdlib_companion/blockstates/logistics.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "mmdlib_companion:block/logistics" + } + } +} diff --git a/src/companion/resources/assets/mmdlib_companion/lang/en_us.json b/src/companion/resources/assets/mmdlib_companion/lang/en_us.json index 49849e3d..950f7c74 100644 --- a/src/companion/resources/assets/mmdlib_companion/lang/en_us.json +++ b/src/companion/resources/assets/mmdlib_companion/lang/en_us.json @@ -48,5 +48,22 @@ "screen.mmdlib_companion.tank.refused": "Cannot transfer fluid. The tank holds water or lava, one type at a time, up to 4 buckets.", "screen.mmdlib_companion.info.crusher": "*Crusher*\n\nCrush ore into raw materials, or stone into gravel and sand.\n\nPlace a power source beside the machine. Pause to release reserved items.\n\nThe left tank holds 4 buckets of water or lava for display. Click the gauge with a fluid container on the cursor to fill or drain it, or right-click the block with a bucket. Empty the tank before changing fluids. Crushing does not use the fluid.", "screen.mmdlib_companion.info.transmuter": "*Iron to gold*\n\nInsert one iron ingot. The same slot holds the gold when the cycle finishes.\n\nSupply power from an adjacent block. Pause to release the input.", - "screen.mmdlib_companion.info.dual_crusher": "*Dual crusher*\n\nTwo lanes share the input and output slots. Each lane reserves its own items and space.\n\nPause either lane independently to release its share." + "screen.mmdlib_companion.info.dual_crusher": "*Dual crusher*\n\nTwo lanes share the input and output slots. Each lane reserves its own items and space.\n\nPause either lane independently to release its share.", + "item.mmdlib_companion.mechanical_puller": "Mechanical Puller", + "item.mmdlib_companion.mechanical_pusher": "Mechanical Pusher", + "item.mmdlib_companion.input_pump": "Input Pump", + "item.mmdlib_companion.output_pump": "Output Pump", + "item.mmdlib_companion.import_relay": "Import Relay", + "item.mmdlib_companion.export_relay": "Export Relay", + "item.mmdlib_companion.item_filter": "Item Filter", + "item.mmdlib_companion.large_item_filter": "Expanded Item Filter", + "item.mmdlib_companion.tag_filter": "Tag Filter", + "item.mmdlib_companion.fluid_filter": "Fluid Filter", + "block.mmdlib_companion.logistics": "Logistics Workbench", + "screen.mmdlib_companion.group.input": "Item input", + "screen.mmdlib_companion.group.output": "Item output", + "screen.mmdlib_companion.group.energy": "Energy", + "screen.mmdlib_companion.group.tank": "Fluid tank %s", + "screen.mmdlib_companion.filter.deny": "Toggle allow-list / deny-list", + "screen.mmdlib_companion.filter.tag": "Item tag (namespace:path); Enter to apply" } diff --git a/src/companion/resources/assets/mmdlib_companion/models/block/logistics.json b/src/companion/resources/assets/mmdlib_companion/models/block/logistics.json new file mode 100644 index 00000000..f699c048 --- /dev/null +++ b/src/companion/resources/assets/mmdlib_companion/models/block/logistics.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:block/cube_all", + "textures": { + "all": "minecraft:block/copper_block" + } +} diff --git a/src/companion/resources/assets/mmdlib_companion/models/item/export_relay.json b/src/companion/resources/assets/mmdlib_companion/models/item/export_relay.json new file mode 100644 index 00000000..a6402781 --- /dev/null +++ b/src/companion/resources/assets/mmdlib_companion/models/item/export_relay.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "minecraft:item/repeater" + } +} diff --git a/src/companion/resources/assets/mmdlib_companion/models/item/fluid_filter.json b/src/companion/resources/assets/mmdlib_companion/models/item/fluid_filter.json new file mode 100644 index 00000000..9b422900 --- /dev/null +++ b/src/companion/resources/assets/mmdlib_companion/models/item/fluid_filter.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "minecraft:item/glass_bottle" + } +} diff --git a/src/companion/resources/assets/mmdlib_companion/models/item/import_relay.json b/src/companion/resources/assets/mmdlib_companion/models/item/import_relay.json new file mode 100644 index 00000000..724d0a33 --- /dev/null +++ b/src/companion/resources/assets/mmdlib_companion/models/item/import_relay.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "minecraft:item/redstone" + } +} diff --git a/src/companion/resources/assets/mmdlib_companion/models/item/input_pump.json b/src/companion/resources/assets/mmdlib_companion/models/item/input_pump.json new file mode 100644 index 00000000..eb3c09c9 --- /dev/null +++ b/src/companion/resources/assets/mmdlib_companion/models/item/input_pump.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "minecraft:item/water_bucket" + } +} diff --git a/src/companion/resources/assets/mmdlib_companion/models/item/item_filter.json b/src/companion/resources/assets/mmdlib_companion/models/item/item_filter.json new file mode 100644 index 00000000..1fa971f1 --- /dev/null +++ b/src/companion/resources/assets/mmdlib_companion/models/item/item_filter.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "minecraft:item/paper" + } +} diff --git a/src/companion/resources/assets/mmdlib_companion/models/item/large_item_filter.json b/src/companion/resources/assets/mmdlib_companion/models/item/large_item_filter.json new file mode 100644 index 00000000..952787b8 --- /dev/null +++ b/src/companion/resources/assets/mmdlib_companion/models/item/large_item_filter.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "minecraft:item/map" + } +} diff --git a/src/companion/resources/assets/mmdlib_companion/models/item/logistics.json b/src/companion/resources/assets/mmdlib_companion/models/item/logistics.json new file mode 100644 index 00000000..8bb53b5b --- /dev/null +++ b/src/companion/resources/assets/mmdlib_companion/models/item/logistics.json @@ -0,0 +1,3 @@ +{ + "parent": "mmdlib_companion:block/logistics" +} diff --git a/src/companion/resources/assets/mmdlib_companion/models/item/mechanical_puller.json b/src/companion/resources/assets/mmdlib_companion/models/item/mechanical_puller.json new file mode 100644 index 00000000..499aca18 --- /dev/null +++ b/src/companion/resources/assets/mmdlib_companion/models/item/mechanical_puller.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "minecraft:item/hopper" + } +} diff --git a/src/companion/resources/assets/mmdlib_companion/models/item/mechanical_pusher.json b/src/companion/resources/assets/mmdlib_companion/models/item/mechanical_pusher.json new file mode 100644 index 00000000..9697ac8f --- /dev/null +++ b/src/companion/resources/assets/mmdlib_companion/models/item/mechanical_pusher.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "minecraft:block/piston_side" + } +} diff --git a/src/companion/resources/assets/mmdlib_companion/models/item/output_pump.json b/src/companion/resources/assets/mmdlib_companion/models/item/output_pump.json new file mode 100644 index 00000000..12186178 --- /dev/null +++ b/src/companion/resources/assets/mmdlib_companion/models/item/output_pump.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "minecraft:item/bucket" + } +} diff --git a/src/companion/resources/assets/mmdlib_companion/models/item/tag_filter.json b/src/companion/resources/assets/mmdlib_companion/models/item/tag_filter.json new file mode 100644 index 00000000..61969bf8 --- /dev/null +++ b/src/companion/resources/assets/mmdlib_companion/models/item/tag_filter.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "minecraft:item/name_tag" + } +} diff --git a/src/companion/resources/data/mmdlib_companion/loot_table/blocks/logistics.json b/src/companion/resources/data/mmdlib_companion/loot_table/blocks/logistics.json new file mode 100644 index 00000000..87822e48 --- /dev/null +++ b/src/companion/resources/data/mmdlib_companion/loot_table/blocks/logistics.json @@ -0,0 +1,19 @@ +{ + "type": "minecraft:block", + "pools": [ + { + "rolls": 1, + "entries": [ + { + "type": "minecraft:item", + "name": "mmdlib_companion:logistics" + } + ], + "conditions": [ + { + "condition": "minecraft:survives_explosion" + } + ] + } + ] +} diff --git a/src/companion/resources/mmdlib/screen/crusher.xml b/src/companion/resources/mmdlib/screen/crusher.xml index d535146e..fee771f2 100644 --- a/src/companion/resources/mmdlib/screen/crusher.xml +++ b/src/companion/resources/mmdlib/screen/crusher.xml @@ -8,7 +8,7 @@ tooltip="screen.mmdlib_companion.tank.tip" tooltip-fallback="Water or lava: {value} / {max} mB. Click with a fluid container to transfer." on-click="mmdlib_companion:tank_container" on-right-click="mmdlib_companion:tank_container"/> - + - + diff --git a/src/companion/resources/mmdlib/screen/dual_crusher.xml b/src/companion/resources/mmdlib/screen/dual_crusher.xml index de6e01d5..31794f90 100644 --- a/src/companion/resources/mmdlib/screen/dual_crusher.xml +++ b/src/companion/resources/mmdlib/screen/dual_crusher.xml @@ -3,7 +3,7 @@ - + - + diff --git a/src/companion/resources/mmdlib/screen/logistics.xml b/src/companion/resources/mmdlib/screen/logistics.xml new file mode 100644 index 00000000..27a0b061 --- /dev/null +++ b/src/companion/resources/mmdlib/screen/logistics.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + diff --git a/src/companion/resources/mmdlib/screen/transmuter.xml b/src/companion/resources/mmdlib/screen/transmuter.xml index 6d46d8ef..646318a7 100644 --- a/src/companion/resources/mmdlib/screen/transmuter.xml +++ b/src/companion/resources/mmdlib/screen/transmuter.xml @@ -4,7 +4,7 @@