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
4 changes: 4 additions & 0 deletions .agent/workflows/wiki-sync.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
---
description: Synchronizes the wiki information (`.\WIKI\MultiBlockEngine.wiki`) with the codebase by comparing the Git history.
---

# Wiki Sync Workflow

Synchronizes the wiki information (`.\WIKI\MultiBlockEngine.wiki`) with the codebase by comparing the Git history.
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
[submodule "addons/mbe-crafting"]
path = addons/mbe-crafting
url = git@github.com:Parallax-Development/MBE-Crafting.git
[submodule "addons/mbe-electrics"]
path = addons/mbe-electrics
url = git@github.com:Parallax-Development/MBE-Electrics.git
[submodule "addons/mbe-ui"]
path = addons/mbe-ui
url = git@github.com:Parallax-Development/MBE-UI.git
Expand Down
1 change: 1 addition & 0 deletions WIKI/MultiBlockEngine.wiki
Submodule MultiBlockEngine.wiki added at c966a5
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ public interface AddonContext {
MultiblockAPI getAPI();

Path getDataFolder();

void setMultiblockDirectory(Path folder);
Path getMultiblockDirectory();

<T> void registerService(Class<T> serviceType, T service);
<T> T getService(Class<T> serviceType);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package dev.darkblade.mbe.api.ui;

import java.nio.file.Path;

/**
* Service registry that allows addons to register external directories
* containing UI panel configurations.
*
* The UI system will iterate through these directories during its load cycle.
*/
public interface PanelDirectoryRegistry {
/**
* Registers an external directory for panel configuration scanning.
*
* @param directory The path to the directory containing panel YAMLs.
*/
void registerDirectory(Path directory);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package dev.darkblade.mbe.api.ui;

import java.util.Optional;

/**
* Service registry that allows addons to programmatically declare which
* UI panel should open for a specific multiblock type.
*/
public interface PanelMappingRegistry {
/**
* Registers a mapping between a multiblock ID and a panel ID.
*
* @param multiblockId The fully qualified multiblock ID (e.g. "mbe-electrics:coal_generator")
* @param panelId The target panel ID (e.g. "coal_generator")
*/
void registerMapping(String multiblockId, String panelId);

/**
* Retrieves the panel ID mapped to the given multiblock ID, if any.
*
* @param multiblockId The fully qualified multiblock ID
* @return An Optional containing the panel ID if mapped
*/
Optional<String> getMapping(String multiblockId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@

public interface DisplayEntityRenderer {
int spawnBlockDisplay(Player player, Location location, BlockData blockData);

default int spawnBlockDisplay(Player player, Location location, BlockData blockData, float tx, float ty, float tz, float sx, float sy, float sz) {
return spawnBlockDisplay(player, location, blockData);
}

void updateBlockDisplay(int entityId, BlockData blockData);

default void highlightError(Player player, int entityId) {
// Fallback does nothing
}

void destroyEntities(Player player, Collection<Integer> entityIds);
}
22 changes: 22 additions & 0 deletions api/src/main/java/dev/darkblade/mbe/preview/PreviewSession.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,17 @@ public final class PreviewSession {
private volatile Rotation rotation;
private volatile PreviewState state;
private volatile Instant lastTouchedAt;
private volatile int currentLayer;
private volatile Vector3i nudgeOffset;

public PreviewSession(UUID playerId, MultiblockDefinition definition, Location origin, Rotation rotation) {
this.playerId = playerId;
this.definition = definition;
this.origin = origin;
this.rotation = rotation == null ? Rotation.NORTH : rotation;
this.state = PreviewState.MOVING;
this.currentLayer = Integer.MAX_VALUE;
this.nudgeOffset = new Vector3i(0, 0, 0);
this.blocks = new ConcurrentHashMap<>();
this.renderVersion = new AtomicLong(0L);
this.lastTouchedAt = Instant.now();
Expand Down Expand Up @@ -124,6 +128,24 @@ public Instant lastTouchedAt() {
return lastTouchedAt;
}

public int currentLayer() {
return currentLayer;
}

public void currentLayer(int layer) {
this.currentLayer = layer;
touch();
}

public Vector3i nudgeOffset() {
return nudgeOffset;
}

public void nudgeOffset(Vector3i offset) {
this.nudgeOffset = offset == null ? new Vector3i(0, 0, 0) : offset;
touch();
}

public void touch() {
this.lastTouchedAt = Instant.now();
}
Expand Down
16 changes: 16 additions & 0 deletions api/src/main/java/dev/darkblade/mbe/preview/Rotation.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,20 @@ public Rotation nextClockwise() {
case WEST -> NORTH;
};
}

public static Rotation fromYaw(float yaw) {
yaw = yaw % 360;
if (yaw < 0) {
yaw += 360;
}
if (yaw >= 45 && yaw < 135) {
return WEST;
} else if (yaw >= 135 && yaw < 225) {
return NORTH;
} else if (yaw >= 225 && yaw < 315) {
return EAST;
} else {
return SOUTH;
}
}
}
8 changes: 8 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,14 @@ subprojects {
maven {
url = 'https://repo.extendedclip.com/content/repositories/placeholderapi/'
}
maven {
name = 'codemc-releases'
url = 'https://repo.codemc.io/repository/maven-releases/'
}
maven {
name = 'codemc-snapshots'
url = 'https://repo.codemc.io/repository/maven-snapshots/'
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ public BlueprintController(
this.eventBus = Objects.requireNonNull(eventBus, "eventBus");
}

public boolean isBlueprint(org.bukkit.inventory.ItemStack item) {
return heldItemResolver.blueprintId(item).isPresent();
}

public boolean handleInput(Player player) {
if (player == null) {
return false;
Expand Down Expand Up @@ -104,6 +108,43 @@ public boolean handleRotation(Player player) {
return true;
}

public boolean handleLayerChange(Player player, int delta) {
if (player == null) {
return false;
}
PlayerBuildContext context = contextService.get(player);
PreviewSession session = context.preview();
if (session == null) {
return false;
}
int layer = session.currentLayer();
if (layer == Integer.MAX_VALUE && delta < 0) {
layer = 0; // If starting to go down, start at 0 (or find the max Y of the definition).
// Let's just do a simple increment/decrement from current if it's max.
// Actually, if it's MAX_VALUE, let's figure out max Y.
if (session.definition() != null) {
layer = 0;
for (dev.darkblade.mbe.preview.PreviewBlock block : session.definition().blocks()) {
if (block.localPosition().y() > layer) {
layer = block.localPosition().y();
}
}
}
}

int newLayer = layer + delta;
if (newLayer < 0) newLayer = 0;
if (newLayer > 256) newLayer = Integer.MAX_VALUE; // Reset to all

if (newLayer != session.currentLayer()) {
session.currentLayer(newLayer);
previewService.updatePreviewOrigin(player, session.origin()); // Forces a re-render
previewService.touch(player);
return true;
}
return false;
}

public boolean handleHeldItem(Player player) {
if (player == null) {
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ public void onInteract(PlayerInteractEvent event) {
return;
}
Player player = event.getPlayer();
if (!controller.isBlueprint(event.getItem())) {
return;
}
if (!controller.handleInput(player)) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,13 @@ public static ItemStack create(ItemService itemService, ItemStackBridge bridge,
if (itemService == null || bridge == null || definition == null || definition.id() == null || definition.id().isBlank()) {
return null;
}
ItemInstance instance = itemService.factory().create(BLUEPRINT_KEY, Map.of(DATA_STRUCTURE_ID, definition.id()));
java.util.Map<String, Object> data = new java.util.HashMap<>();
data.put(DATA_STRUCTURE_ID, definition.id());
data.put("multiblock_display_name", definition.id());
data.put("count", 0);
data.put("total", definition.blocks() != null ? definition.blocks().size() : 0);

ItemInstance instance = itemService.factory().create(BLUEPRINT_KEY, data);
ItemStack stack = bridge.toItemStack(instance, sender);
ItemMeta meta = stack.getItemMeta();
if (meta != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,23 @@ public void onPlayerMove(PlayerMoveEvent event) {
}
controller.updatePreviewOnMove(player);
}

@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onItemSwitch(org.bukkit.event.player.PlayerItemHeldEvent event) {
Player player = event.getPlayer();
if (player.isSneaking()) {
int previous = event.getPreviousSlot();
int current = event.getNewSlot();
// Calculate scroll direction.
// If going from 0 to 8, they scrolled left (decrement)
// If going from 8 to 0, they scrolled right (increment)
int delta = current - previous;
if (previous == 0 && current == 8) delta = -1;
if (previous == 8 && current == 0) delta = 1;

if (controller.handleLayerChange(player, delta)) {
event.setCancelled(true);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,12 @@ public void onEnable() {
// Load definitions
log.setCorePhase(LogPhase.LOAD);
List<MultiblockParser.LoadedType> loadedTypes = parser.loadAllWithSources(multiblockDir);
for (java.nio.file.Path addonDir : addonManager.getAddonMultiblockDirectories()) {
if (java.nio.file.Files.exists(addonDir)) {
loadedTypes.addAll(parser.loadAllWithSources(addonDir.toFile()));
}
}

List<MultiblockType> types = new ArrayList<>(loadedTypes.size());
for (MultiblockParser.LoadedType loaded : loadedTypes) {
if (loaded == null || loaded.type() == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ public MBECommandManager(Plugin owningPlugin) {
)
);

this.parameterInjectorRegistry().registerInjector(
CommandSender.class,
(context, annotations) -> context.sender().getSender()
);

this.parameterInjectorRegistry().registerInjector(
org.bukkit.entity.Player.class,
(context, annotations) -> context.sender().getPlayer()
);

this.annotationParser = new org.incendo.cloud.annotations.AnnotationParser<>(
this,
MBESender.class
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ public void list(MBESender mbeSender) {

@Command("mbe blueprint give <id> [target]")
@Permission("multiblockengine.blueprint")
public void give(MBESender mbeSender, @Argument("id") String id, @Argument("target") Player targetArg) {
public void give(MBESender mbeSender, @Argument("id") org.bukkit.NamespacedKey idKey, @Argument("target") Player targetArg) {
String id = idKey.toString();
CommandSender sender = mbeSender.getSender();
Player receiver = targetArg;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,16 @@ public List<AddonRuntime> listLoadedAddons() {
}
}

public List<Path> getAddonMultiblockDirectories() {
List<Path> dirs = new ArrayList<>();
for (LoadedAddon loaded : registry.loadedAddons.values()) {
if (loaded.context() != null && loaded.context().getMultiblockDirectory() != null) {
dirs.add(loaded.context().getMultiblockDirectory());
}
}
return dirs;
}

public List<AddonInfo> getAddonInfoList() {
List<AddonInfo> result = new ArrayList<>();
List<String> allIds = new ArrayList<>(registry.states.keySet());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ public AddonRuntimeLifecycleService(MultiBlockEngine plugin, MultiblockAPI api,

private String missingRequiredEnabledDependencies(AddonMetadata meta) {
for (String req : meta.requiredDependencies().keySet()) {
if (registry.states.getOrDefault(req, AddonState.DISABLED) != AddonState.ENABLED) {
AddonState state = registry.states.getOrDefault(req, AddonState.DISABLED);
if (state != AddonState.ENABLED && state != AddonState.LOADED) {
return req;
}
}
Expand Down Expand Up @@ -345,7 +346,7 @@ public void loadAddon(DiscoveredAddon discovered) throws IOException {
return;
}

registry.loadedAddons.put(addonId, new LoadedAddon(metadata, addon, loader, addonLogger, phaseRef, dataFolder));
registry.loadedAddons.put(addonId, new LoadedAddon(metadata, addon, loader, addonLogger, phaseRef, dataFolder, context));
registry.states.put(addonId, AddonState.LOADED);
addonLogger.withPhase(LogPhase.LOAD).info("Loaded", LogKv.kv("version", metadata.version().toString()));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public class SimpleAddonContext implements AddonContext {
private final AddonServiceRegistry services;
private final ServiceLifecycleOrchestrator serviceLifecycleManager;
private final ClassLoader classLoader;
private Path multiblockDirectory;

public SimpleAddonContext(
String addonId,
Expand Down Expand Up @@ -95,6 +96,16 @@ public Path getDataFolder() {
return dataFolder;
}

@Override
public void setMultiblockDirectory(Path folder) {
this.multiblockDirectory = folder;
}

@Override
public Path getMultiblockDirectory() {
return multiblockDirectory;
}

@Override
public <T> void registerService(Class<T> serviceType, T service) {
services.register(addonId, serviceType, service);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ public record LoadedAddon(
dev.darkblade.mbe.core.application.service.addon.AddonClassLoader classLoader,
AddonLogger logger,
AtomicReference<LogPhase> phase,
Path dataFolder) {}
Path dataFolder,
dev.darkblade.mbe.api.addon.AddonContext context) {}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ public String getServiceId() {

@Override
public void registerPanel(PanelId id, PanelDefinition panel) {
if (addonLifecycleService.getCurrentLifecyclePhase() != ServiceLifecycleOrchestrator.LifecyclePhase.CONTENT_REGISTRATION) {
ServiceLifecycleOrchestrator.LifecyclePhase phase = addonLifecycleService.getCurrentLifecyclePhase();
if (phase != ServiceLifecycleOrchestrator.LifecyclePhase.CONTENT_REGISTRATION && phase != ServiceLifecycleOrchestrator.LifecyclePhase.RUNTIME) {
throw new IllegalStateException("Panel registration outside allowed phase");
}
registry.registerPanel(id, panel);
Expand Down
Loading
Loading