Components Synchronization
Feature: Components Synchronization
Summary
Allow puzzle creators to synchronize two compatible components so that a property change triggered by a player on one is automatically propagated to the other. This opens new puzzle mechanics (synchronized mirrors, paired rotating parts, entangled gravity zones) and reduces repetitive setup.
Not related to a specific Minecraft version.
Feature Description
A sync is a persistent, bidirectional association between two components, scoped to a single property. When a player (not an editor) changes that property on one component, the same change is automatically applied to the other.
⚠️ Editor actions are excluded from propagation. Rotating or modifying a component while in editor mode (isInEditorMode() == true) does not propagate to synced components. Only in-game player actions trigger the sync.
Supported sync types (v1)
| Sync type | Required interface / class (each component independently) | Propagated change |
|---|---|---|
ROTATION_HORIZONTAL |
Both implement IRotatableComponent
|
LEFT and RIGHT rotations |
ROTATION_VERTICAL |
Both implement IRotatableComponent
|
UP and DOWN rotations |
ROTATION_ALL |
Both implement IRotatableComponent
|
All 4 rotations (full coupling) |
MIRROR |
Both implement IMirrorContainer
|
Mirror placement / removal / color change |
MIRROR_AND_ROTATION_ALL |
Both implement IRotatableComponent & IMirrorContainer
|
4 rotations & mirror |
SIZE |
Both are GravitationalSphere
|
Gravity strength change |
Components of different types can be synced, provided they share the required interface. Examples: a
MirrorSupport(implementsIRotatableComponentandIMirrorContainer) can be synced with aConcentrator(implementsIRotatableComponent) via a ROTATION sync, or with aFilteringSphere(implementsIMirrorContainer) via a MIRROR sync.
Bidirectionality
Syncs are always bidirectional. Propagation flows from whichever component was modified by the player. There is no "master/slave" distinction.
N-way synchronization (3 or more components)
To synchronize N components fully, the creator must create one sync per pair. For 3 components A, B, C this means 3 syncs: A
This is required because propagation triggered by a player on A fires with ActionCause.PLAYER, which propagates to B and C directly via AActionCause.SYNC and do not re-propagate further (anti-loop mechanism). Without A
✅ With all 3 pairwise syncs in place, modifying any one of the 3 components always syncs the other two — regardless of which one the player interacts with.
To allow puzzle makers to synchronize N components fully, we should add a menu that would appear like a confirmation window during the pairing process. This menu should ask if the puzzle creator wants to propagate the sync to other components having already a synchronization with one of the components to sync. This window would appear only the same sync type is already used to pair one of the two components we are paring with another one. The question would be something like "Do you want to propage the synchronization to all related components or just pair those 2 components ?". Ex: A <=> B are already sync. The puzzle creator add a sync B <=> C. The system detects the pre-existing A<=>B link and because of it, it displays the confirmation window. In this window if the player clicks "NO propagation", it will add only the sync B <=> C. If he clicks "propagation" in the confirmation menu, it will add the sync B <=> C but it will also add the sync A <=> C.
Propagation loop prevention
A SYNC value must be added to the ActionCause enum (alongside existing PLAYER, SCHEDULED_ACTION, COMMAND). When a change is propagated via a sync, it is applied with ActionCause.SYNC. The sync listener must only react to ActionCause.PLAYER, preventing infinite propagation loops.
Constraints
- Both components must belong to the same area (cross-area syncs are out of scope for v1).
- A component cannot be synced to itself.
- Duplicate syncs (same pair, same type) are rejected with an error message.
- No hard-coded maximum on the number of syncs per component.
Black mirror special case (MIRROR sync)
When a MIRROR sync exists between a MirrorSupport and a FilteringSphere (or ReflectingSphere), the FilteringSphere does not support LasersColor.BLACK (a black mirror would block all lasers indiscriminately).
In this situation, inserting a black mirror into the MirrorSupport must be actively blocked with a clear error message, since the propagation to the synced sphere cannot be fulfilled. The block happens at the player action level (before any modification is applied), not silently after the fact.
A new method boolean supportsColor(LasersColor) must be added to IMirrorContainer to allow this check cleanly.
Motivation and Benefits
- Enables new synchronized puzzle mechanics impossible before (twin mirrors, entangled rotating parts).
- Reduces the number of identical components a creator must configure — a single player action propagates to all synced components at once.
- Naturally discoverable through the existing editor UX without adding commands.
Use Cases
-
Twin rotating mirrors: A
MirrorSupportsynced to anotherMirrorSupportviaROTATION_ALL— a player rotating one mirror automatically rotates the other. -
Mirror support + concentrator coupling: A
MirrorSupportsynced to aConcentratorviaROTATION_HORIZONTAL— a player turning the mirror left/right also turns the concentrator in the same direction. Rotating either component vertically (up/down) has no effect on the other, since the sync is horizontal only. -
Synchronized color filter: A
MirrorSupportsynced to aFilteringSphereviaMIRROR— placing a colored mirror in the support instantly places the same color in the sphere, and conversely, inserting a mirror directly into the sphere also inserts it into the mirror support. From the player's perspective, only one mirror is needed to activate both components, and only one mirror is returned when extracting from either of them. The mirror economy is unchanged. -
Partial rotation coupling: Two
LaserSendercomponents synced viaROTATION_VERTICAL— a player can tilt them up/down together while adjusting them independently left/right. -
Mirrored gravity zones: Two
GravitationalSpherecomponents synced viaSIZE— adjusting the attraction radius of one automatically adjusts the other.
UI/UX Design
1. New item in MainShortcutBarInventory
Add a MAIN_SHORTCUTBAR_SYNC item and restructure the bar:
Before: PLACE_COMPONENT | EMPTY | AREA_CONFIG | STATS_MENU | EMPTY | CREATE_AREA | DELETE_AREA | EMPTY | CLOSE
After: PLACE_COMPONENT | SYNC_TOOL | EMPTY | AREA_CONFIG | STATS_MENU | CREATE_AREA | DELETE_AREA | EMPTY | CLOSE
Item description: "Used to display, add, or delete syncs between components."
Additionally, the following contextual visibility rules apply to the bar (evaluated each time it is opened):
-
CREATE_AREAis hidden (replaced byEMPTY) when the player is inside an existing area. -
DELETE_AREAis hidden (replaced byEMPTY) when the player is outside any area.
2. Sync visualization (passive — while holding the Sync Tool)
While the player holds the Sync Tool item:
- For each unique component pair that shares at least one sync, spawn a single line of
ItemDisplayentities (sync/chain-link head texture) visible only to the holding player (via ProtocolLib, mirroring theRotationPreviewRendererpattern). The line goes from component A to component B, stopping just before each component's block on both ends. - For each sync type on that pair, spawn one
TextDisplayentity at the midpoint showing the sync type. If the pair has multiple syncs (e.g.,ROTATION_ALL+MIRROR), theTextDisplayentities are stacked with a 0.35-block vertical offset per entry. TheItemDisplayline is shared and not duplicated:-
⟳ ROTATION (H)/⟳ ROTATION (V)/⟳ ROTATION (ALL) ⟳ MIRROR⟳ SIZE
-
- All entities are removed immediately when the player switches item slot or exits editor mode.
- If no syncs exist in the area, a subtle action-bar message: "No syncs in this area. Right-click a component to create one."
3. Opening the sync manager (right-clicking a component)
When the player right-clicks a component while holding the Sync Tool item, instead of opening the usual ComponentShortcutBarInventory, it opens a ComponentSyncsMenuInventory (chest GUI, paginated with AHorizontallyPaginableOpenableInventory).
This inventory shows:
- One slot per existing sync for this component (see §4).
- A permanent "Add sync" button.
- A permanent "Back" button returning to
MainShortcutBarInventory.
4. Sync entry items in ComponentSyncsMenuInventory
Each sync is represented by a player-head item with name and lore:
-
Name:
⟳ [SYNC TYPE](e.g.,⟳ ROTATION (ALL)) -
Lore line 1:
Synced with: [ComponentType] (x, y, z) -
Lore line 2:
[Left-click] Delete this sync
Left-clicking a sync entry opens a SyncDeletionConfirmationInventory (consistent with the existing DeleteAreaConfirmationInventory pattern).
5. Sync creation workflow
From ComponentSyncsMenuInventory, clicking "Add sync":
-
Opens a
SyncCreationShortcutBarInventory(AShortcutBarInventorysubclass):- Slot 0:
SYNC_CREATION_AIM_AND_SYNC— "Aim at a component and click to sync". - Slots 1–7: empty.
- Slot 8:
SYNC_CREATION_CANCEL— returns toComponentSyncsMenuInventoryfor component A.
- Slot 0:
-
The player aims at component B and clicks the button.
-
The system resolves the component at the targeted block using
getLastTwoTargetBlocks()(consistent withKeyChestCreationShortcutBarInventory). -
A
SyncTypeSelectorMenuInventory(chest GUI) opens showing:- Compatible sync types as clickable items.
- Incompatible types as grayed-out items with an explanatory lore line.
-
Player clicks a sync type → the sync is created and a chat message is sent to the player confirming the creation:
[ComponentTypeA] @ (xA, yA, zA) ⟳ [SYNC TYPE] ⟳ [ComponentTypeB] @ (xB, yB, zB)The view then returns to
ComponentSyncsMenuInventoryfor component A.
Error messages (via TranslationUtils, all as chat messages):
- No component found at target location
- Target component is in a different area
- Cannot sync a component with itself
- This sync already exists
- No compatible sync types between the two components
6. Sync section in ComponentMenuInventory (discoverability)
Add a "Syncs" button at the bottom of the existing ComponentMenuInventory chest GUI. It opens ComponentSyncsMenuInventory for the current component. This provides a second discovery path for creators who approach sync management from the component menu.
Out of Scope (v1)
- Cross-area syncs (both components must be in the same area)
- Command-line interface for sync management (GUI only)
- Sync export/import via schematics
Open Questions — Resolved
-
MIRROR sync +
MirrorSupportMode.BLOCKED: ABLOCKEDmirror support cannot be moved by the player directly, but it can move indirectly through a synced component. TheBLOCKEDmode constraint applies only to direct player interactions, not to sync propagation. -
Existing
correspondingComponentIdinComponentEntity: This must not be reused for syncs. The existing field is a 1-to-1 tight coupling (Lock↔️ KeyChest, Elevator↔️ CallButton). Syncs are 1-to-N and property-specific → completely separate tablecomponent_sync. -
Multiple syncs between the same component pair: A single
ItemDisplayline is drawn between the two components (not duplicated). Only theTextDisplaylabels are multiplied — one per sync type, stacked with a 0.35-block vertical offset. -
Area reset behavior: Each component resets independently to its own saved state. As always, the state saved by the editor is the authoritative reference and the one used on reset. Sync propagation plays no role in the reset.