Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 103 additions & 6 deletions docs/gui-screens.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,18 +66,31 @@ contents, and asks the client block entity's `screenItems()` what each slot acce
holds, so a click is predicted the way the server will answer it. It's only asked about rules, so its
contents don't need to be synced.

Return the `ItemStorage` itself. A wrapper around it, or another `ReservedSlots` implementation,
loses the guards below and the client's copy of the plans.
Return the `ItemStorage` itself, or a handler/wrapper implementing `ReservedSlots`, including
`plan(slot)`, so both menu sides receive and enforce the same reservations.

When the machine has reserved items in a slot (see [capabilities](capabilities.md#reservations)),
the menu keeps them from being taken or replaced:

- Left and right click, Q and Ctrl+Q, and shift-click take only what is free.
- Clicking the slot with a different item is refused.
- A number key is refused on a slot with a plan, because vanilla would move the whole stack.
- Number-key and offhand swaps move only the unreserved portion.
- Double-clicking a slot with a plan doesn't collect from elsewhere. Double-clicking another slot
still takes the planned slot's free part.

Machine slots show reservations at the top right. Both reservation and held quantities use
87.5% of vanilla's font size:
`-2` means two items reserved for input, and `+1` means space reserved for one output. The held
quantity stays at the bottom right, using the same size even when no reservation is present.
An empty planned output shows its item model at 50%
opacity with `+1` at the top, without creating a real stack. Mixed consume/produce plans show
both signed counts on the top row. Tooltips provide reservation counts and the planned output's
name. Clearing the plan removes the badge and ghost. Player inventory, equipment and filter
slots are unaffected.
Chance outputs show their reserved capacity, not a guarantee that the product will be rolled.
No extra packet or XML binding is needed; `MachineMenu.reservationPlan(index)` exposes the existing
synchronized snapshot for real machine slots.

The client's menu works out the same answer from the slot's plan, which arrives with the screen's
values, so clicks don't flicker. Bars and numbers still come from the getters named in the XML; a
feature's own client sync (see [block entities](block-entities.md#machine-features)) doesn't
Expand Down Expand Up @@ -338,7 +351,7 @@ payload contains both lane index and state. The server restricts that selector t
in its XML property. These settings persist with the machine. The graphical smoke run checks
focus, typing, cancellation, resizing, view changes, release outside bounds and server rejection.

The expanded menu protocol is version **6**, including a session token in menu-opening data.
The expanded menu protocol is version **9**, including a session token in menu-opening data.
Client and server must use matching MMDLib builds.

## Files and IDs
Expand Down Expand Up @@ -446,7 +459,7 @@ inventory slots keeps the machine's `screenDestinations` routing. JEI recipe tra
only the ordinary inventory and hotbar, leaving equipped items alone.

Optional `<inventory-controls>` goes after the main body and before tabs/popouts. Its button
children form a right-aligned row above the player inventory (for example redstone and pause
children form a right-aligned row above the player inventory (for example manual pause
toggles). Automation's reusable `InventoryColorButtonBar` shares that row on the left, with
scrollable overflow. Both sides align with the nine-column storage grid, leaving the equipment
column clear. Flat controls fit in the inventory's existing top row; taller controls add only
Expand Down Expand Up @@ -783,7 +796,10 @@ darker icon rail as the other drawers. Large inventories are paged with the arro
between eligible equipped containers. The main panel stays stationary, opening storage
closes a left information drawer, and the right automation drawer remains independent.
If the window has insufficient space, the tab explains how to make room. Unequipping,
replacing or resizing a container closes the drawer; stale clicks are rejected by the server.
replacing or resizing a container closes the drawer; requests for an old container or page are
rejected by the server. Live item-count or icon updates do not cancel drags or block clicks:
transfers use the current server contents, filters and limits. Both equipment drawers share one
menu-local Curios discovery per tick, refreshed before and after item actions.
The drawer respects the capability's insertion/extraction filters and limits.

Some items expose storage only after being initialized through their own normal UI. Such
Expand Down Expand Up @@ -817,6 +833,87 @@ to the available slot groups or counts closes the old view and invalidates stale
reopening shows the updated slots. Curios stays optional, with no tab or class-loading
requirement when absent. This drawer does not change slot definitions or rendering toggles.

## Redstone face drawer

Implement `ScreenHost.screenRedstone()` to return a persistent `RedstoneControl`. Its right-side
drawer uses the inventory drawer's neighbour grid, directional badges and shared frame. Clicking
a different face selects it; clicking the selected face cycles Ignore, Read and Emit (right-click
cycles backwards). Faces have a spaced selection outline and small background-free signal numbers:
bright green incoming strength for Read, orange emitted strength for Emit, with thin glyph outlines.

- **Ignore:** no input gate and no emitted signal.
- **Read:** a single bar has a minimum handle above and a maximum below. Endpoints are inclusive,
and dragging either past the other pushes both together. Between highlights the selected interval;
Outside highlights its complement. The icon toggle sits to the left of the bar: inward arrows
between boundaries mean Between, outward arrows beyond boundaries mean Outside. Equal endpoints
simplify only the info text to For N / Except N; ranges touching 0 or 15 use a single
above/below condition (including equality for Between). The icon stays unchanged. Processing pauses when
the face receives a highlighted level.
The default is Between 1–15. Dragging previews locally, release applies both endpoints atomically,
and Escape cancels the drag. Any Read face requesting pause holds processing.
- **Emit:** click the colour swatch or sprite and source name to cycle outputs; right-click
cycles backwards. Resource fullness entries come first in the companion, followed by each
processor's own Processing, Paused/idle and Progress entries. A small reverse-arrows button
before the picker toggles Revert (`15 - level`). Until a source is selected, or if that source
no longer exists, the face emits zero even when reverted.

These controls are built in: there are no redstone upgrade items or slots. Each face saves its
interval, Between/Outside choice, stable output ID and inversion independently. Changing the
face mode retains those settings. The small yellow info box explains the selected source and
current signal. The drawer fits its content and scrolls when the viewport cannot fit it. The
old main redstone button is removed; manual pause remains above the inventory. Right drawers
are mutually exclusive, and drawer/face selection is remembered for the current connection.

### Output providers

Runtime parts opt in by implementing `api.redstone.IRedstoneOutput`. Its `redstoneOutputs()`
returns a stable, ordered list of `RedstoneOutput` descriptors. Each descriptor has:

- A stable ID unique across this machine, used for saved selections.
- Translatable name and description components, used by the picker and yellow info box.
- An RGB colour and optional GUI sprite ID, used as the picker's swatch or icon.
- A server-side `IntSupplier` whose result is clamped to 0-15.

Construct `RedstoneControl(owner, parts)` after registering the parts; it discovers only parts
implementing the interface and rejects duplicate IDs. The catalog must have the same order and
metadata on client and server. Descriptors use common-side types, without client rendering
classes. Suppliers are evaluated only on the server, once per selected source per output update.
`FeatureHost` implements the interface by collecting outputs from participating features.

`RedstoneOutput.resource(group, fullnessSupplier)` adapts an existing `ResourceGroup`, preserving
its ID, label and colour. Fullness is 0-1 and maps to `floor(fullness * 15)`. The companion measures
item groups by average slot fullness, fluid groups by amount/capacity, and energy by stored/capacity.
The resource-only `RedstoneControl(owner, groups, fullnessFunction)` constructor remains available.

Each `RecipeProcessor` contributes three independent sources, prefixed by its saved feature key:

| Source | Signal |
|---|---|
| Processing | 15 while actively processing (including the start and completion ticks), otherwise 0 |
| Paused/idle | 15 while paused, idle, blocked, energy-stalled or held by the host; otherwise 0 |
| Progress | `floor(completedTicks / recipeTicks * 15)`; retained while held/paused, 15 on completion, 0 when idle |

Use the constructor accepting a display-name `Component` to label processors clearly; the original
constructor defaults to the feature key. Multiple processors produce multiple sets of entries.
There is no aggregate machine-wide running signal.

Tick in this order on the server:

```java
redstone.readInputs();
features.tick(level, !redstone.paused() && otherProcessingGates);
// Perform any automatic transfers here.
redstone.updateOutputs();
```

Passing `false` to `FeatureHost.tick` invokes each feature's `suspended` callback instead of
processing it, so a held processor cannot retain a stale running signal. Save/load the controller
tag and return `signal(direction)` from the block's redstone output hook. The direction passed
to `signal` is Minecraft's query direction, not the outward face. Stateless hosts can call
`redstone.tick()` to read inputs and update outputs together. The companion emits weak power,
keeps transfers running while processing is held, and migrates old inventory selections and
the previous global redstone mode. Manual pause remains independent.

## Proper use

These are guidance for screen authors, not rules MMDLib can check:
Expand Down
3 changes: 3 additions & 0 deletions docs/gui-sprites.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ icons and progress arrows are full colour.
| `icon_pause`, `icon_resume` | 16×16 | none | Gold pause bars and green play triangle; `_a` and `_b` variants identify independent lanes |
| `button_flat`, `button_flat_highlighted` | 14×14 | nine-slice, border 1 | Compact button with a uniform dark outline and flat grey fill; hover lightens only the interior |
| `icon_filter_whitelist`, `icon_filter_blacklist` | 10×10 | none | White/black list cards on the filter's flat grey mode toggle |
| `icon_processor_progress` | 16x16 | none | Three rising blue bars for per-processor recipe progress. |
| `icon_redstone_ignore`, `icon_redstone_high`, `icon_redstone_low` | 16×16 | none | Redstone torch with × / + / − mode badge |
| `icon_redstone_between`, `icon_redstone_outside` | 32x12 | none | Transparent interval glyphs with cream boundaries and green inward/outward arrows. |
| `icon_redstone_revert`, `icon_redstone_revert_on` | 14x14 | none | Opposed horizontal arrows for output inversion; cream when off, green when on. |
| `icon_info` | 16×16 | none | Pop-out `info`: white serif "i" with a dark outline and transparent background |
| `icon_warning` | 16×16 | none | Pop-out `warning`: yellow triangle, black "!" |
| `icon_error` | 16×16 | none | Pop-out `error`: red heater shield, white "!" |
Expand Down
12 changes: 10 additions & 2 deletions docs/machine-companion.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ to run without it.

| Block ID (prefix `mmdlib_companion:`) | What it does |
| --- | --- |
| `crusher` | One input slot, one output slot, one processor, and a water/lava preview tank |
| `crusher` | One input slot, two output slots, one processor, and a water/lava preview tank |
| `transmuter` | One slot limited to one item; converts an iron ingot into a gold ingot |
| `dual_crusher` | Two independently pausable processors share the same input and output slots |
| `creative_power` | Refills its buffer and supplies up to 1,000 FE per adjacent face per tick |
Expand All @@ -29,6 +29,14 @@ sand, and copper/iron/gold ore becomes two of the corresponding raw material. Th
200 ticks and 4,000 FE. The dual crusher runs the same recipes in both lanes when there are enough
inputs and output space.

The Crusher's two outputs share one inventory and colour/side configuration. For testing, both
allow insertion and extraction, so you can manually fill them with any item; reservations still
protect promised output space. New Crushers default this group to input/output on all faces. Outputs may
split across the slots: with 63 raw iron in each output, one iron ore reserves `+1` in each slot
and finishes at 64 + 64. With only one free space total, that two-item recipe waits without taking
input or energy. Existing Crusher saves retain their input and first output, with the new output
starting empty. The Dual Crusher keeps its existing shared single-output layout.

The bottom of each processor panel has a compact, right-aligned row of icon buttons above the inventory,
with thin dark borders and flat grey backgrounds.
Click the pause icon to interrupt a lane and release its reservations; it becomes a green play
Expand All @@ -43,7 +51,7 @@ inside to read longer text. All four screens place a vertical battery on the rig
reusable `<energy-gauge>` XML widget. Its red charge bars fill upwards inside a slim casing.
The energy and fluid gauges share matching edges and medium-grey empty backgrounds, darker than slots.
Energy bars are thin with a soft reflection; cylindrical glass shading covers only the fluid.
The Crusher centers its input, a circular progress dial and output alongside it. The dial is a
The Crusher centers its input, a circular progress dial and two adjacent output slots. The dial is a
custom widget registered by the companion through MMDLib's [widget API](gui-screens.md#registering-custom-widget-types).
Click the dial to pause or resume; its tooltip shows progress in ticks. The Crusher's Recipes
button opens crushing recipes when JEI is installed. The other processors retain their progress
Expand Down
71 changes: 71 additions & 0 deletions scripts/generate_gui_sprites.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,74 @@ def glyph(img, rows, x, y, ink):
write_png("icon_redstone_" + mode, img)


def write_processor_progress_icon():
"""Three rising blue bars inside a small progress frame."""
img = canvas(16, 16)
rect(img, 1, 2, 14, 12, (30, 40, 52, 255))
rect(img, 2, 3, 12, 10, (92, 108, 124, 255))
for x, height in ((3, 3), (7, 6), (11, 9)):
rect(img, x, 12 - height, 2, height, (133, 210, 255, 255))
rect(img, x, 12 - height, 2, 1, (213, 241, 255, 255))
write_png("icon_processor_progress", img)


def write_redstone_revert_icons():
"""Opposed horizontal arrows for normal/reverted resource output, transparent 14x14."""
rows = ("......#...", "......##..", "#########.", "......##..", "......#...")
ink = (30, 40, 34, 255)
for active in (False, True):
img = canvas(14, 14)
for y0, right in ((1, True), (8, False)):
for y, row in enumerate(rows):
for x, bit in enumerate(row if right else row[::-1]):
if bit == "#":
img[y0 + y][x + 2] = ((173, 255, 192, 255) if active
else (255, 239, 183, 255))
result = canvas(14, 14)
for y, row in enumerate(img):
for x, colour in enumerate(row):
if colour == CLEAR: continue
for dx, dy in ((-1, 0), (1, 0), (0, -1), (0, 1)):
nx, ny = x + dx, y + dy
if 0 <= nx < 14 and 0 <= ny < 14: result[ny][nx] = ink
for y, row in enumerate(img):
for x, colour in enumerate(row):
if colour != CLEAR: result[y][x] = colour
write_png("icon_redstone_revert" + ("_on" if active else ""), result)


def write_redstone_range_icons():
"""32x12 transparent interval glyphs, matching the outlined machine-control sprites."""
ink = (30, 40, 34, 255)
arrow_rows = ("...#...", "...##..", "######.", "#######",
"######.", "...##..", "...#...")
for mode in ("between", "outside"):
img = canvas(32, 12)
bars = (2, 28) if mode == "between" else (12, 18)
arrows = ((7, True), (18, False)) if mode == "between" else ((2, False), (23, True))
for x in bars:
rect(img, x, 2, 2, 8, (210, 186, 121, 255))
rect(img, x, 2, 1, 7, (255, 239, 183, 255))
for x, right in arrows:
for y, row in enumerate(arrow_rows):
for dx, bit in enumerate(row if right else row[::-1]):
if bit == "#":
img[y + 2][x + dx] = ((173, 255, 192, 255) if y < 3
else (86, 218, 127, 255) if y < 5
else (53, 165, 96, 255))
result = canvas(32, 12)
for y, row in enumerate(img):
for x, colour in enumerate(row):
if colour == CLEAR: continue
for dx, dy in ((-1, 0), (1, 0), (0, -1), (0, 1)):
nx, ny = x + dx, y + dy
if 0 <= nx < 32 and 0 <= ny < 12: result[ny][nx] = ink
for y, row in enumerate(img):
for x, colour in enumerate(row):
if colour != CLEAR: result[y][x] = colour
write_png("icon_redstone_" + mode, result)


def write_side_config_sprites():
"""Outlined directional glyphs remain legible over faded neighbour previews."""
cyan, orange = (133, 210, 255, 255), (255, 185, 101, 255)
Expand Down Expand Up @@ -361,6 +429,9 @@ def main():
write_png("compact_frame", compact_frame(), {"type": "nine_slice", "width": 8, "height": 8, "border": 1})
write_drawer_sprites()
write_control_icons()
write_redstone_range_icons()
write_redstone_revert_icons()
write_processor_progress_icon()
write_filter_icons()
write_flat_buttons()
write_side_config_sprites()
Expand Down
9 changes: 0 additions & 9 deletions src/companion/java/zone/moddev/mc/companion/CompanionMod.java
Original file line number Diff line number Diff line change
Expand Up @@ -93,15 +93,6 @@ public CompanionMod(IEventBus bus) {
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
Expand Down
16 changes: 16 additions & 0 deletions src/companion/java/zone/moddev/mc/companion/DemoBlock.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,22 @@ public DemoBlock(Properties properties, Supplier<BlockEntityType<DemoEntity>> ty
this.type = type;
}

@Override
protected boolean isSignalSource(BlockState state) {
return true;
}

@Override
protected int getSignal(
BlockState state,
net.minecraft.world.level.BlockGetter level,
BlockPos pos,
net.minecraft.core.Direction side) {
return level.getBlockEntity(pos) instanceof ProcessorEntity entity
? entity.redstone.signal(side)
: 0;
}

@Override
public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
return type.get().create(pos, state);
Expand Down
Loading
Loading