Skip to content

Commit ff3d64c

Browse files
authored
Merge pull request #94 from MinecraftModDevelopmentMods/codex/redstone-control-drawer
Add per-face redstone drawer and machine reservation overlays
2 parents a9867db + 36cddf0 commit ff3d64c

56 files changed

Lines changed: 3483 additions & 410 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎docs/gui-screens.md‎

Lines changed: 103 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,18 +66,31 @@ contents, and asks the client block entity's `screenItems()` what each slot acce
6666
holds, so a click is predicted the way the server will answer it. It's only asked about rules, so its
6767
contents don't need to be synced.
6868

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

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

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

81+
Machine slots show reservations at the top right. Both reservation and held quantities use
82+
87.5% of vanilla's font size:
83+
`-2` means two items reserved for input, and `+1` means space reserved for one output. The held
84+
quantity stays at the bottom right, using the same size even when no reservation is present.
85+
An empty planned output shows its item model at 50%
86+
opacity with `+1` at the top, without creating a real stack. Mixed consume/produce plans show
87+
both signed counts on the top row. Tooltips provide reservation counts and the planned output's
88+
name. Clearing the plan removes the badge and ghost. Player inventory, equipment and filter
89+
slots are unaffected.
90+
Chance outputs show their reserved capacity, not a guarantee that the product will be rolled.
91+
No extra packet or XML binding is needed; `MachineMenu.reservationPlan(index)` exposes the existing
92+
synchronized snapshot for real machine slots.
93+
8194
The client's menu works out the same answer from the slot's plan, which arrives with the screen's
8295
values, so clicks don't flicker. Bars and numbers still come from the getters named in the XML; a
8396
feature's own client sync (see [block entities](block-entities.md#machine-features)) doesn't
@@ -338,7 +351,7 @@ payload contains both lane index and state. The server restricts that selector t
338351
in its XML property. These settings persist with the machine. The graphical smoke run checks
339352
focus, typing, cancellation, resizing, view changes, release outside bounds and server rejection.
340353

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

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

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

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

836+
## Redstone face drawer
837+
838+
Implement `ScreenHost.screenRedstone()` to return a persistent `RedstoneControl`. Its right-side
839+
drawer uses the inventory drawer's neighbour grid, directional badges and shared frame. Clicking
840+
a different face selects it; clicking the selected face cycles Ignore, Read and Emit (right-click
841+
cycles backwards). Faces have a spaced selection outline and small background-free signal numbers:
842+
bright green incoming strength for Read, orange emitted strength for Emit, with thin glyph outlines.
843+
844+
- **Ignore:** no input gate and no emitted signal.
845+
- **Read:** a single bar has a minimum handle above and a maximum below. Endpoints are inclusive,
846+
and dragging either past the other pushes both together. Between highlights the selected interval;
847+
Outside highlights its complement. The icon toggle sits to the left of the bar: inward arrows
848+
between boundaries mean Between, outward arrows beyond boundaries mean Outside. Equal endpoints
849+
simplify only the info text to For N / Except N; ranges touching 0 or 15 use a single
850+
above/below condition (including equality for Between). The icon stays unchanged. Processing pauses when
851+
the face receives a highlighted level.
852+
The default is Between 1–15. Dragging previews locally, release applies both endpoints atomically,
853+
and Escape cancels the drag. Any Read face requesting pause holds processing.
854+
- **Emit:** click the colour swatch or sprite and source name to cycle outputs; right-click
855+
cycles backwards. Resource fullness entries come first in the companion, followed by each
856+
processor's own Processing, Paused/idle and Progress entries. A small reverse-arrows button
857+
before the picker toggles Revert (`15 - level`). Until a source is selected, or if that source
858+
no longer exists, the face emits zero even when reverted.
859+
860+
These controls are built in: there are no redstone upgrade items or slots. Each face saves its
861+
interval, Between/Outside choice, stable output ID and inversion independently. Changing the
862+
face mode retains those settings. The small yellow info box explains the selected source and
863+
current signal. The drawer fits its content and scrolls when the viewport cannot fit it. The
864+
old main redstone button is removed; manual pause remains above the inventory. Right drawers
865+
are mutually exclusive, and drawer/face selection is remembered for the current connection.
866+
867+
### Output providers
868+
869+
Runtime parts opt in by implementing `api.redstone.IRedstoneOutput`. Its `redstoneOutputs()`
870+
returns a stable, ordered list of `RedstoneOutput` descriptors. Each descriptor has:
871+
872+
- A stable ID unique across this machine, used for saved selections.
873+
- Translatable name and description components, used by the picker and yellow info box.
874+
- An RGB colour and optional GUI sprite ID, used as the picker's swatch or icon.
875+
- A server-side `IntSupplier` whose result is clamped to 0-15.
876+
877+
Construct `RedstoneControl(owner, parts)` after registering the parts; it discovers only parts
878+
implementing the interface and rejects duplicate IDs. The catalog must have the same order and
879+
metadata on client and server. Descriptors use common-side types, without client rendering
880+
classes. Suppliers are evaluated only on the server, once per selected source per output update.
881+
`FeatureHost` implements the interface by collecting outputs from participating features.
882+
883+
`RedstoneOutput.resource(group, fullnessSupplier)` adapts an existing `ResourceGroup`, preserving
884+
its ID, label and colour. Fullness is 0-1 and maps to `floor(fullness * 15)`. The companion measures
885+
item groups by average slot fullness, fluid groups by amount/capacity, and energy by stored/capacity.
886+
The resource-only `RedstoneControl(owner, groups, fullnessFunction)` constructor remains available.
887+
888+
Each `RecipeProcessor` contributes three independent sources, prefixed by its saved feature key:
889+
890+
| Source | Signal |
891+
|---|---|
892+
| Processing | 15 while actively processing (including the start and completion ticks), otherwise 0 |
893+
| Paused/idle | 15 while paused, idle, blocked, energy-stalled or held by the host; otherwise 0 |
894+
| Progress | `floor(completedTicks / recipeTicks * 15)`; retained while held/paused, 15 on completion, 0 when idle |
895+
896+
Use the constructor accepting a display-name `Component` to label processors clearly; the original
897+
constructor defaults to the feature key. Multiple processors produce multiple sets of entries.
898+
There is no aggregate machine-wide running signal.
899+
900+
Tick in this order on the server:
901+
902+
```java
903+
redstone.readInputs();
904+
features.tick(level, !redstone.paused() && otherProcessingGates);
905+
// Perform any automatic transfers here.
906+
redstone.updateOutputs();
907+
```
908+
909+
Passing `false` to `FeatureHost.tick` invokes each feature's `suspended` callback instead of
910+
processing it, so a held processor cannot retain a stale running signal. Save/load the controller
911+
tag and return `signal(direction)` from the block's redstone output hook. The direction passed
912+
to `signal` is Minecraft's query direction, not the outward face. Stateless hosts can call
913+
`redstone.tick()` to read inputs and update outputs together. The companion emits weak power,
914+
keeps transfers running while processing is held, and migrates old inventory selections and
915+
the previous global redstone mode. Manual pause remains independent.
916+
820917
## Proper use
821918

822919
These are guidance for screen authors, not rules MMDLib can check:

‎docs/gui-sprites.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,10 @@ icons and progress arrows are full colour.
3131
| `icon_pause`, `icon_resume` | 16×16 | none | Gold pause bars and green play triangle; `_a` and `_b` variants identify independent lanes |
3232
| `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 |
3333
| `icon_filter_whitelist`, `icon_filter_blacklist` | 10×10 | none | White/black list cards on the filter's flat grey mode toggle |
34+
| `icon_processor_progress` | 16x16 | none | Three rising blue bars for per-processor recipe progress. |
3435
| `icon_redstone_ignore`, `icon_redstone_high`, `icon_redstone_low` | 16×16 | none | Redstone torch with × / + / − mode badge |
36+
| `icon_redstone_between`, `icon_redstone_outside` | 32x12 | none | Transparent interval glyphs with cream boundaries and green inward/outward arrows. |
37+
| `icon_redstone_revert`, `icon_redstone_revert_on` | 14x14 | none | Opposed horizontal arrows for output inversion; cream when off, green when on. |
3538
| `icon_info` | 16×16 | none | Pop-out `info`: white serif "i" with a dark outline and transparent background |
3639
| `icon_warning` | 16×16 | none | Pop-out `warning`: yellow triangle, black "!" |
3740
| `icon_error` | 16×16 | none | Pop-out `error`: red heater shield, white "!" |

‎docs/machine-companion.md‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ to run without it.
1919

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

32+
The Crusher's two outputs share one inventory and colour/side configuration. For testing, both
33+
allow insertion and extraction, so you can manually fill them with any item; reservations still
34+
protect promised output space. New Crushers default this group to input/output on all faces. Outputs may
35+
split across the slots: with 63 raw iron in each output, one iron ore reserves `+1` in each slot
36+
and finishes at 64 + 64. With only one free space total, that two-item recipe waits without taking
37+
input or energy. Existing Crusher saves retain their input and first output, with the new output
38+
starting empty. The Dual Crusher keeps its existing shared single-output layout.
39+
3240
The bottom of each processor panel has a compact, right-aligned row of icon buttons above the inventory,
3341
with thin dark borders and flat grey backgrounds.
3442
Click the pause icon to interrupt a lane and release its reservations; it becomes a green play
@@ -43,7 +51,7 @@ inside to read longer text. All four screens place a vertical battery on the rig
4351
reusable `<energy-gauge>` XML widget. Its red charge bars fill upwards inside a slim casing.
4452
The energy and fluid gauges share matching edges and medium-grey empty backgrounds, darker than slots.
4553
Energy bars are thin with a soft reflection; cylindrical glass shading covers only the fluid.
46-
The Crusher centers its input, a circular progress dial and output alongside it. The dial is a
54+
The Crusher centers its input, a circular progress dial and two adjacent output slots. The dial is a
4755
custom widget registered by the companion through MMDLib's [widget API](gui-screens.md#registering-custom-widget-types).
4856
Click the dial to pause or resume; its tooltip shows progress in ticks. The Crusher's Recipes
4957
button opens crushing recipes when JEI is installed. The other processors retain their progress

‎scripts/generate_gui_sprites.py‎

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,74 @@ def glyph(img, rows, x, y, ink):
298298
write_png("icon_redstone_" + mode, img)
299299

300300

301+
def write_processor_progress_icon():
302+
"""Three rising blue bars inside a small progress frame."""
303+
img = canvas(16, 16)
304+
rect(img, 1, 2, 14, 12, (30, 40, 52, 255))
305+
rect(img, 2, 3, 12, 10, (92, 108, 124, 255))
306+
for x, height in ((3, 3), (7, 6), (11, 9)):
307+
rect(img, x, 12 - height, 2, height, (133, 210, 255, 255))
308+
rect(img, x, 12 - height, 2, 1, (213, 241, 255, 255))
309+
write_png("icon_processor_progress", img)
310+
311+
312+
def write_redstone_revert_icons():
313+
"""Opposed horizontal arrows for normal/reverted resource output, transparent 14x14."""
314+
rows = ("......#...", "......##..", "#########.", "......##..", "......#...")
315+
ink = (30, 40, 34, 255)
316+
for active in (False, True):
317+
img = canvas(14, 14)
318+
for y0, right in ((1, True), (8, False)):
319+
for y, row in enumerate(rows):
320+
for x, bit in enumerate(row if right else row[::-1]):
321+
if bit == "#":
322+
img[y0 + y][x + 2] = ((173, 255, 192, 255) if active
323+
else (255, 239, 183, 255))
324+
result = canvas(14, 14)
325+
for y, row in enumerate(img):
326+
for x, colour in enumerate(row):
327+
if colour == CLEAR: continue
328+
for dx, dy in ((-1, 0), (1, 0), (0, -1), (0, 1)):
329+
nx, ny = x + dx, y + dy
330+
if 0 <= nx < 14 and 0 <= ny < 14: result[ny][nx] = ink
331+
for y, row in enumerate(img):
332+
for x, colour in enumerate(row):
333+
if colour != CLEAR: result[y][x] = colour
334+
write_png("icon_redstone_revert" + ("_on" if active else ""), result)
335+
336+
337+
def write_redstone_range_icons():
338+
"""32x12 transparent interval glyphs, matching the outlined machine-control sprites."""
339+
ink = (30, 40, 34, 255)
340+
arrow_rows = ("...#...", "...##..", "######.", "#######",
341+
"######.", "...##..", "...#...")
342+
for mode in ("between", "outside"):
343+
img = canvas(32, 12)
344+
bars = (2, 28) if mode == "between" else (12, 18)
345+
arrows = ((7, True), (18, False)) if mode == "between" else ((2, False), (23, True))
346+
for x in bars:
347+
rect(img, x, 2, 2, 8, (210, 186, 121, 255))
348+
rect(img, x, 2, 1, 7, (255, 239, 183, 255))
349+
for x, right in arrows:
350+
for y, row in enumerate(arrow_rows):
351+
for dx, bit in enumerate(row if right else row[::-1]):
352+
if bit == "#":
353+
img[y + 2][x + dx] = ((173, 255, 192, 255) if y < 3
354+
else (86, 218, 127, 255) if y < 5
355+
else (53, 165, 96, 255))
356+
result = canvas(32, 12)
357+
for y, row in enumerate(img):
358+
for x, colour in enumerate(row):
359+
if colour == CLEAR: continue
360+
for dx, dy in ((-1, 0), (1, 0), (0, -1), (0, 1)):
361+
nx, ny = x + dx, y + dy
362+
if 0 <= nx < 32 and 0 <= ny < 12: result[ny][nx] = ink
363+
for y, row in enumerate(img):
364+
for x, colour in enumerate(row):
365+
if colour != CLEAR: result[y][x] = colour
366+
write_png("icon_redstone_" + mode, result)
367+
368+
301369
def write_side_config_sprites():
302370
"""Outlined directional glyphs remain legible over faded neighbour previews."""
303371
cyan, orange = (133, 210, 255, 255), (255, 185, 101, 255)
@@ -361,6 +429,9 @@ def main():
361429
write_png("compact_frame", compact_frame(), {"type": "nine_slice", "width": 8, "height": 8, "border": 1})
362430
write_drawer_sprites()
363431
write_control_icons()
432+
write_redstone_range_icons()
433+
write_redstone_revert_icons()
434+
write_processor_progress_icon()
364435
write_filter_icons()
365436
write_flat_buttons()
366437
write_side_config_sprites()

‎src/companion/java/zone/moddev/mc/companion/CompanionMod.java‎

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -93,15 +93,6 @@ public CompanionMod(IEventBus bus) {
9393
id("tank_container"),
9494
MaterialEventTypes.GUI_CLICK,
9595
CompanionMod::transferTankContainer);
96-
MaterialExtensions.GLOBAL
97-
.events()
98-
.registerCallback(
99-
id("redstone"),
100-
MaterialEventTypes.GUI_CLICK,
101-
click -> {
102-
if (click.menu().blockEntity instanceof ProcessorEntity machine)
103-
machine.cycleRedstoneMode();
104-
});
10596
for (int lane = 0; lane < 2; lane++) {
10697
int index = lane;
10798
MaterialExtensions.GLOBAL

‎src/companion/java/zone/moddev/mc/companion/DemoBlock.java‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,22 @@ public DemoBlock(Properties properties, Supplier<BlockEntityType<DemoEntity>> ty
3232
this.type = type;
3333
}
3434

35+
@Override
36+
protected boolean isSignalSource(BlockState state) {
37+
return true;
38+
}
39+
40+
@Override
41+
protected int getSignal(
42+
BlockState state,
43+
net.minecraft.world.level.BlockGetter level,
44+
BlockPos pos,
45+
net.minecraft.core.Direction side) {
46+
return level.getBlockEntity(pos) instanceof ProcessorEntity entity
47+
? entity.redstone.signal(side)
48+
: 0;
49+
}
50+
3551
@Override
3652
public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
3753
return type.get().create(pos, state);

0 commit comments

Comments
 (0)