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
18 changes: 18 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ repositories {
mavenCentral()
// JEI (optional integration; compile-time API only)
maven { url = 'https://maven.blamejared.com' }
maven {
url = 'https://maven.theillusivec4.top/'
content { includeGroup 'top.theillusivec4.curios' }
}
maven {
url = 'https://api.modrinth.com/maven'
content { includeGroup 'maven.modrinth' }
}
}

// Dev-only runtime dependencies: on the runtime classpath, but kept out of the published POM.
Expand All @@ -34,6 +42,8 @@ configurations {
}

dependencies {
// Optional equipment discovery; neither bundled nor a transitive runtime requirement.
compileOnly "top.theillusivec4.curios:curios-neoforge:${curios_version}:api"
// Optional JEI integration: compiled against the API only, never bundled or required.
compileOnly "mezz.jei:jei-1.21.1-common-api:${jei_version}"
compileOnly "mezz.jei:jei-1.21.1-neoforge-api:${jei_version}"
Expand All @@ -54,6 +64,13 @@ neoForge {
runs {
configureEach {
gameDirectory = file('run')
// Optional user-level preferences apply only to manual development clients.
if (name in ['client', 'companionClient']) {
if (providers.gradleProperty('mmdlibClientWidth').isPresent())
programArguments.addAll '--width', providers.gradleProperty('mmdlibClientWidth').get()
if (providers.gradleProperty('mmdlibClientHeight').isPresent())
programArguments.addAll '--height', providers.gradleProperty('mmdlibClientHeight').get()
}
}
client {
client()
Expand Down Expand Up @@ -92,6 +109,7 @@ tasks.named('processResources', ProcessResources) {
neoforge_version_range: neoforge_version_range,
loader_version_range: loader_version_range,
jei_version_range: jei_version_range,
curios_version_range: curios_version_range,
mod_id: mod_id,
mod_name: mod_name,
mod_license: mod_license,
Expand Down
107 changes: 100 additions & 7 deletions docs/gui-screens.md
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,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 **2**, including a session token in menu-opening data.
The expanded menu protocol is version **6**, including a session token in menu-opening data.
Client and server must use matching MMDLib builds.

## Files and IDs
Expand Down Expand Up @@ -431,14 +431,26 @@ a compact meter, a few controls, an upgrade tab, and a side-configuration view:
</screen>
```

The standard player inventory (three rows of nine, a 4 px gap, then the hotbar) is added at the
bottom automatically. Write `<player-inventory/>` only to place it somewhere else, or set
`inventory="none"` on `<screen>` to leave it out.
The standard player inventory is added at the bottom automatically: three rows of nine,
a 4 px gap, then the hotbar. A column on the right contains helmet, chestplate, leggings and
boots, with the offhand slot beside the hotbar. Empty equipment slots show vanilla icons;
armor uses NeoForge's equipment checks, a one-item limit, and vanilla binding-curse protection.
The complete inventory panel is 184 by 94 GUI pixels, including its toolbar row. There is no
inventory label: the toolbar shares the helmet's row, directly above the three storage rows.
Write `<player-inventory/>` only to place it somewhere else, or set `inventory="none"` on
`<screen>` to leave out the inventory and equipment together.

Shift-clicking equipment moves it into the ordinary inventory/hotbar. Machine outputs also
go there; they do not automatically equip armor or fill the offhand. Shift-clicking ordinary
inventory slots keeps the machine's `screenDestinations` routing. JEI recipe transfer uses
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
toggles). Automation's reusable `InventoryColorButtonBar` shares that row on the left, with
scrollable overflow. The row is allocated once, and both sides align with the inventory edges.
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
the extra height they need.
Use ordinary button declarations inside it; no spacer or coordinates are needed. This element
requires a player inventory and is available to every screen, not just companion machines.

Expand Down Expand Up @@ -473,7 +485,9 @@ requires a player inventory and is available to every screen, not just companion
- A button with `shows="<context>:<view>"` switches that context area to the view. Pressing
it again, or the back button drawn in the corner of every non-default view, returns to the
default view. Escape also returns to the default view first, then closes the screen.
- The area is sized to its largest view, so switching never moves the rest of the screen.
- The area measures the active view and fills the available width in vertical layouts.
Switching views resizes and recentres the panel, moving the inventory, toolbar and drawers
with it. Hidden settings do not reserve empty space; menu slot identities stay unchanged.
- A view's `title` is drawn at its top, so the player can always tell which view is showing.
- The shown view is remembered per screen file for the session.
- Slots in a hidden view exist on the server menu at all times, but are hidden and refuse
Expand Down Expand Up @@ -711,7 +725,9 @@ When JEI is installed:
Pop-out tabs are 22×24 rectangles, coloured by kind: yellow information, amber warning,
red error. Clicking one opens a matching drawer outward from that edge, with the icon
retained on a darker rail as the close control. Its body is up to 150 pixels wide and
fits its text, capped at the main panel's bottom and the screen edge. Text wraps to the available width; scroll inside
fits its text. Info and inventory drawers share a 4-pixel inset from the main panel's top
and bottom, and stop earlier if the screen or an overlay limits the space. The side-config
drawer shares the top inset but can grow below the main panel to fit its filter controls. Text wraps to the available width; scroll inside
to read longer content. Opening another drawer on the same edge replaces the open drawer;
tabs remain visible and clickable on the rail.

Expand All @@ -724,6 +740,83 @@ When JEI is installed:
sends a smeltable item to its ingredient slot and other fuel to its fuel slot. MMDLib only
keeps slots in hidden views out of that routing.

Drawer choices are remembered across MMDLib machine screens for the current connection.
The left drawer (info, backpack or Curios) and right automation drawer are independent.
Backpack selection uses its equipment slot address, and automation selection uses its resource
group ID. Missing features are skipped; explicit closes are remembered. Joining another world
or server resets these preferences. Filter-editor drafts are not carried between machines.

## Equipped storage drawer (optional Curios integration)

When Curios is installed, machine screens with a player inventory automatically show a
bottom-left tab for an equipped item exposing NeoForge's `Capabilities.ItemHandler.ITEM`.
The tab renders the actual item, including its model/tint components and custom name in
the tooltip. No screen XML changes or backpack-specific API are needed.

The server config `inventory.container_slots` selects functional Curios slots to inspect;
the defaults are `back` and `backpack`. Cosmetic slots are excluded. Empty slots, ordinary
items and zero-slot capabilities show no tab. Curios remains optional, and neither Curios
nor any backpack mod is bundled with MMDLib.

Click the tab to open storage on the left. Left/right clicks take a stack/half stack or
insert the carried stack/one item. Shift-click a storage cell to move items into the player
inventory; **while storage is open, shift-clicking the ordinary player inventory stores
items in the selected container**. Machine-output and equipment transfers keep their usual
behavior. Drag with the left button to divide the carried stack evenly, or with the right
button to place one item in each visited slot. Drags starting in the backpack can include ordinary
player inventory slots. Gestures starting in the normal inventory retain Minecraft's usual drag
and double-click behavior. Press a configured hotbar key over a backpack slot to exchange its contents with that
hotbar slot. Double-click a backpack stack to collect matching items from the entire selected backpack
and ordinary player inventory, up to the carried item's stack limit. Item components,
insertion/extraction restrictions and slot limits are respected. All transfers use only the
container's insert/extract capability. A container may redirect insertions (for example,
merging into existing stacks instead of the hovered empty slots), including during drags
and hotbar swaps. Drag previews highlight backpack targets; final counts come from the container
after release. The drawer does not bypass that behavior with direct slot setters.

Backpack-specific upgrades/settings/crafting screens are not provided. Ordinary clicks with
different carried items do not swap stacks; use a hotbar key for an explicit swap.

Storage uses six slots per row inside a drawer filling the panel height between the shared
4-pixel top and bottom insets, with the same tinted frame and
darker icon rail as the other drawers. Large inventories are paged with the arrows or mouse wheel. Click the header to cycle
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.
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
items show the tab only after initialization. The library does not initialize third-party
storage or interpret its private data.

For development, `runCompanionClient` and `runCompanionClientSmoke` include Curios,
Sophisticated Backpacks and Sophisticated Core as runtime fixtures. Use
`-PwithoutBackpacks` to omit them. These dependencies do not enter either published jar.
Server GameTests run without these fixtures because Curios login sync requires a negotiated
client connection; the client smoke test covers real backpack discovery and networked transfers.

## Curios equipment drawer

With Curios installed, screens that show player inventory also gain a **Curios** tab on
the left. It sits above the backpack tab when an equipped container is available. The
full-height drawer lists the player's visible Curios slot groups in their configured order,
using Curios' names and empty-slot icons. The **E** column contains functional equipment;
the **C** column contains cosmetic equipment where the slot type supports it. A dash means
that slot has no cosmetic counterpart. Arrows and the mouse wheel page through longer lists.

Left/right clicking equips or unequips through the actual Curios handlers. Shift-clicking
an accessory moves it into ordinary player storage. While this drawer is open, shift-clicking
player storage attempts to equip the item in a compatible **functional** slot; place cosmetic
items directly in the cosmetic column. Existing equipment is not swapped automatically.
Curios' validation, equip/unequip events and removal restrictions still apply.

Opening this drawer closes backpack storage or the left information drawer; the right
automation drawer remains independent. Escape closes the equipment drawer first. A change
to the available slot groups or counts closes the old view and invalidates stale requests;
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.

## Proper use

These are guidance for screen authors, not rules MMDLib can check:
Expand Down
7 changes: 4 additions & 3 deletions docs/inventory-automation.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@ on the left; inventory side configuration stays on the right. Filter controls oc
scrollable region inside the configuration drawer below the addon slots. The arrow bar beneath
the installed filter shows/hides this region; it has no duplicate selected-filter header.
The drawer fits its
visible controls and grows downward to fit an open filter, stopping four GUI pixels above the
screen edge (with extra clearance for JEI's bottom toolbar when visible); overflow scrolls within
visible controls and grows below the main panel as needed to fit an open filter, stopping at
the screen margin or JEI toolbar limit; overflow scrolls within
the filter region. The colour selector keeps its own compact
height. Info drawers fit their text up to the main panel's bottom or the screen edge, whichever
height. All drawers share the same frame renderer and four-pixel top inset. The configuration
drawer can grow to the available screen height; inventory drawers retain the panel's bottom inset. Info drawers fit their text up to the inset bottom or the screen edge, whichever
comes first.

## Define independent resource groups
Expand Down
6 changes: 6 additions & 0 deletions docs/machine-companion.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ in `src/companion` and are packaged separately from the library.
## Play

Run `./gradlew runCompanionClient`, create a creative world, and open the **MMDLib Machines** tab.

For a personal window size, add `mmdlibClientWidth=2560` and `mmdlibClientHeight=1440` to
`~/.gradle/gradle.properties` (or the file in your `GRADLE_USER_HOME`). These optional settings
apply to `runClient` and `runCompanionClient` only and stay outside the repository. Without them,
Minecraft uses its default window size. Automated smoke runs retain their own fixed dimensions.

Place a **Creative Power Source** next to a machine, then right-click the machine to open its screen.
The source supplies all six adjacent faces. JEI is included in development runs; add `-PwithoutJei`
to run without it.
Expand Down
6 changes: 6 additions & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ parchment_mappings_version = 2024.11.17
jei_version = 19.57.0.451
jei_version_range = [19,)

# Optional equipment API; backpack mods are companion runtime fixtures only.
curios_version = 9.5.1+1.21.1
curios_version_range = [9.5.1,10)
sophisticated_backpacks_version = 1.21.1-3.26.20.2244
sophisticated_core_version = 1.21.1-1.5.28.2458

# Gradle
org.gradle.jvmargs = -Xmx3G
org.gradle.daemon = false
Expand Down
27 changes: 25 additions & 2 deletions scripts/companion.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,26 @@ sourceSets {
compileClasspath += sourceSets.companion.output + sourceSets.companion.compileClasspath
runtimeClasspath += sourceSets.companion.output + sourceSets.companion.runtimeClasspath
}
// Mock GameTest connections cannot negotiate Curios' login-sync payloads.
// Reuse the compiled fixtures, but keep optional mods for real client/server smoke runs.
companionServerTest {
runtimeClasspath += sourceSets.companionTest.output + sourceSets.companion.output + sourceSets.main.output + sourceSets.main.runtimeClasspath
}
}

neoForge.addModdingDependenciesTo(sourceSets.companion)
neoForge.addModdingDependenciesTo(sourceSets.companionTest)
neoForge.addModdingDependenciesTo(sourceSets.companionServerTest)

// Development fixtures only: do not add backpack APIs or required mod metadata to either jar.
// -PwithoutBackpacks retains a companion run without Curios or backpack mods.
if (!providers.gradleProperty('withoutBackpacks').isPresent()) {
dependencies {
companionRuntimeOnly "top.theillusivec4.curios:curios-neoforge:${curios_version}"
companionRuntimeOnly "maven.modrinth:sophisticated-backpacks:${sophisticated_backpacks_version}"
companionRuntimeOnly "maven.modrinth:sophisticated-core:${sophisticated_core_version}"
}
}

neoForge {
mods {
Expand Down Expand Up @@ -40,7 +56,7 @@ neoForge {
companionGameTestServer {
type = 'gameTestServer'
gameDirectory = layout.buildDirectory.dir('companion-gametest')
sourceSet = sourceSets.companionTest
sourceSet = sourceSets.companionServerTest
loadedMods = [neoForge.mods.named(mod_id).get(), neoForge.mods.mmdlib_companion_test]
systemProperty 'neoforge.enabledGameTestNamespaces', 'mmdlib_companion'
}
Expand Down Expand Up @@ -69,7 +85,7 @@ tasks.matching { it.name == 'runCompanionClientSmoke' }.configureEach {
doFirst {
def smokeDir = layout.buildDirectory.dir('companion-client-smoke').get().asFile
smokeDir.mkdirs()
new File(smokeDir, 'options.txt').text = 'guiScale:2\nmaxFps:30\nonboardAccessibility:false\n'
new File(smokeDir, 'options.txt').text = 'guiScale:2\nmaxFps:30\nonboardAccessibility:false\npauseOnLostFocus:false\n'
}
doLast {
def log = layout.buildDirectory.file('companion-client-smoke/logs/latest.log').get().asFile
Expand All @@ -94,6 +110,13 @@ tasks.register('verifyCompanionPackaging') {
new java.util.zip.ZipFile(tasks.named('jar').get().archiveFile.get().asFile).withCloseable { jar ->
def leaked = jar.entries().findAll { it.name.contains('mmdlib_companion') || it.name.startsWith('zone/moddev/mc/companion/') }
if (leaked) throw new GradleException('Companion content in MMDLib jar: ' + leaked*.name)
def bundledIntegrations = jar.entries().findAll { it.name.startsWith('top/theillusivec4/') || it.name.startsWith('net/p3pp3rf1y/') }
if (bundledIntegrations) throw new GradleException('Optional integration classes bundled in MMDLib: ' + bundledIntegrations*.name)
def metadata = jar.getInputStream(jar.getEntry('META-INF/neoforge.mods.toml')).getText('UTF-8')
if (!(metadata =~ /(?s)modId="curios"\s+type="optional"/).find())
throw new GradleException('Curios must be declared optional in the release jar')
if (metadata.contains('sophisticatedbackpacks') || metadata.contains('sophisticatedcore'))
throw new GradleException('Backpack test fixtures must not be dependencies of MMDLib')
}
new java.util.zip.ZipFile(companionJar.get().archiveFile.get().asFile).withCloseable { jar ->
def leaked = jar.entries().findAll { it.name.startsWith('zone/moddev/mc/lib/') || it.name.startsWith('zone/moddev/mc/companion/test/') || it.name.contains('/structure/') }
Expand Down
2 changes: 1 addition & 1 deletion scripts/gui-smoke.init.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ allprojects {
def smokeDir = file(providers.gradleProperty('withoutJei').isPresent() ? 'build/gui-smoke-without-jei' : 'build/gui-smoke')
smokeDir.mkdirs()
new File(smokeDir, 'options.txt').text = 'guiScale:2\nmaxFps:30\nonboardAccessibility:false\n'
neoForge.runs.client.programArguments.addAll '--width', '1280', '--height', '960'
neoForge.runs.client.programArguments.set(['--width', '1280', '--height', '960'])
neoForge.runs.client.gameDirectory = smokeDir
neoForge.runs.client.systemProperty 'mmdlib.guiSmoke', 'true'
tasks.configureEach { task ->
Expand Down
Loading
Loading