diff --git a/.gitignore b/.gitignore
index 26c7461d..c68c7b85 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,3 +32,5 @@ mcmodsrepo
# datagen caches
**/.cache
+
+repo/
diff --git a/build.gradle b/build.gradle
index ab4730dd..59cb1157 100644
--- a/build.gradle
+++ b/build.gradle
@@ -216,4 +216,4 @@ publishMods {
content = changelog.map { "# ${mod_name} v${mod_version} for MC ${minecraft_version} has been released! \n" + it}
// setPlatforms(platforms.curseforge, platforms.modrinth)
}
-}
\ No newline at end of file
+}
diff --git a/src/main/java/me/desht/modularrouters/api/event/ExecuteModuleEvent.java b/src/main/java/me/desht/modularrouters/api/event/ExecuteModuleEvent.java
new file mode 100644
index 00000000..ed541527
--- /dev/null
+++ b/src/main/java/me/desht/modularrouters/api/event/ExecuteModuleEvent.java
@@ -0,0 +1,58 @@
+package me.desht.modularrouters.api.event;
+
+import me.desht.modularrouters.block.tile.ModularRouterBlockEntity;
+import me.desht.modularrouters.logic.compiled.CompiledModule;
+import net.neoforged.bus.api.Event;
+import net.neoforged.bus.api.ICancellableEvent;
+
+/**
+ * Called when a router {@link me.desht.modularrouters.logic.compiled.CompiledModule#execute(ModularRouterBlockEntity) executes} a module.
+ */
+public class ExecuteModuleEvent extends Event implements ICancellableEvent {
+ private boolean executed;
+ private final ModularRouterBlockEntity router;
+ private final CompiledModule module;
+
+ public ExecuteModuleEvent(ModularRouterBlockEntity router, CompiledModule module) {
+ this.router = router;
+ this.module = module;
+ }
+
+ /**
+ * {@return the router executing the module}
+ */
+ public ModularRouterBlockEntity getRouter() {
+ return router;
+ }
+
+ /**
+ * {@return the executed module}
+ */
+ public CompiledModule getModule() {
+ return module;
+ }
+
+ /**
+ * If set to {@code true}, the router will consider the module executed.
+ *
+ * @apiNote to prevent the module itself from being executed, you need to {@link #setCanceled(boolean) cancel} the event too
+ */
+ public void setExecuted(boolean executed) {
+ this.executed = executed;
+ }
+
+ /**
+ * {@return whether the router should consider the module executed}
+ */
+ public boolean isExecuted() {
+ return this.executed;
+ }
+
+ /**
+ * Cancel this event to prevent the module from executing.
+ */
+ @Override
+ public void setCanceled(boolean canceled) {
+ ICancellableEvent.super.setCanceled(canceled);
+ }
+}
diff --git a/src/main/java/me/desht/modularrouters/api/event/RegisterRouterContainerData.java b/src/main/java/me/desht/modularrouters/api/event/RegisterRouterContainerData.java
new file mode 100644
index 00000000..eb57138a
--- /dev/null
+++ b/src/main/java/me/desht/modularrouters/api/event/RegisterRouterContainerData.java
@@ -0,0 +1,48 @@
+package me.desht.modularrouters.api.event;
+
+import me.desht.modularrouters.block.tile.ModularRouterBlockEntity;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.world.inventory.ContainerData;
+import net.minecraft.world.inventory.DataSlot;
+import net.neoforged.bus.api.Event;
+import org.jetbrains.annotations.ApiStatus;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Fired to register {@link DataSlot data slots} for the router menu.
+ * Use this to sync data to display in the GUI.
+ */
+public class RegisterRouterContainerData extends Event {
+ private final ModularRouterBlockEntity router;
+ private final Map data = new HashMap<>();
+
+ @ApiStatus.Internal
+ public RegisterRouterContainerData(ModularRouterBlockEntity router) {
+ this.router = router;
+ }
+
+ /**
+ * Register a {@link DataSlot data slot}.
+ *
+ * @param id the ID of the slot
+ * @param dataSlot the slot to register
+ */
+ public void register(ResourceLocation id, DataSlot dataSlot) {
+ data.put(id, dataSlot);
+ }
+
+ /**
+ * {@return the router of the menu}
+ */
+ public ModularRouterBlockEntity getRouter() {
+ return router;
+ }
+
+ @ApiStatus.Internal
+ public Map getData() {
+ return Collections.unmodifiableMap(data);
+ }
+}
diff --git a/src/main/java/me/desht/modularrouters/block/tile/ModularRouterBlockEntity.java b/src/main/java/me/desht/modularrouters/block/tile/ModularRouterBlockEntity.java
index ad026135..405390f5 100644
--- a/src/main/java/me/desht/modularrouters/block/tile/ModularRouterBlockEntity.java
+++ b/src/main/java/me/desht/modularrouters/block/tile/ModularRouterBlockEntity.java
@@ -33,6 +33,7 @@
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.GlobalPos;
+import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtUtils;
@@ -59,6 +60,7 @@
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.AABB;
import net.neoforged.neoforge.client.model.data.ModelData;
+import net.neoforged.neoforge.common.NeoForge;
import net.neoforged.neoforge.energy.EnergyStorage;
import net.neoforged.neoforge.energy.IEnergyStorage;
import net.neoforged.neoforge.fluids.capability.IFluidHandlerItem;
@@ -114,7 +116,7 @@ public class ModularRouterBlockEntity extends BlockEntity implements ICamouflage
private byte recompileNeeded = COMPILE_MODULES | COMPILE_UPGRADES;
private int tickRate = ConfigHolder.common.router.baseTickRate.get();
private int itemsPerTick = 1;
- private final Map- upgradeCount = new HashMap<>();
+ private final Map upgradeCount = new HashMap<>();
private int fluidTransferRate; // mB/t
private int fluidTransferRemainingIn = 0;
@@ -186,6 +188,13 @@ public CompoundTag getUpdateTag() {
if (nEnergy > 0) {
tag.putInt(NBT_ENERGY_UPGRADES, nEnergy);
}
+
+ getAllUpgrades().keySet().forEach(item -> {
+ final var updateTag = item.createUpdateTag(this);
+ if (updateTag != null) {
+ tag.put(BuiltInRegistries.ITEM.getKey(item).toString(), updateTag);
+ }
+ });
});
}
@@ -214,6 +223,11 @@ private void processClientSync(CompoundTag compound) {
}
energyStorage.updateForEnergyUpgrades(compound.getInt(NBT_ENERGY_UPGRADES));
+
+ getAllUpgrades().keySet().forEach(item -> {
+ final var updateTag = compound.get(BuiltInRegistries.ITEM.getKey(item).toString());
+ item.processClientSync(this, (CompoundTag) updateTag);
+ });
}
@Override
@@ -397,7 +411,24 @@ private boolean runAllModules(boolean powered, boolean pulsed) {
for (CompiledIndexedModule cim : compiledModules) {
CompiledModule cm = cim.compiledModule;
- if (cm != null && cm.hasTarget() && cm.getEnergyCost() <= getEnergyStorage().getEnergyStored() && cm.shouldRun(powered, pulsed))
+ if (cm != null && cm.hasTarget() && cm.getEnergyCost() <= getEnergyStorage().getEnergyStored() && cm.shouldRun(powered, pulsed)) {
+ var event = cm.getEvent();
+ if (event != null) {
+ event.setExecuted(false);
+ event.setCanceled(false);
+ NeoForge.EVENT_BUS.post(event);
+ if (event.isExecuted()) {
+ newActive = true;
+ }
+
+ if (event.isCanceled()) {
+ if ((newActive && cm.termination() == ModuleItem.Termination.RAN) || cm.termination() == ModuleItem.Termination.NOT_RAN) {
+ break;
+ }
+ continue;
+ }
+ }
+
if (cm.execute(this)) {
cm.getFilter().cycleRoundRobin().ifPresent(counter -> {
ItemStack moduleStack = modulesHandler.getStackInSlot(cim.index);
@@ -411,6 +442,7 @@ private boolean runAllModules(boolean powered, boolean pulsed) {
} else if (cm.termination() == ModuleItem.Termination.NOT_RAN) {
break;
}
+ }
}
return newActive;
}
@@ -550,7 +582,7 @@ private void compileUpgrades() {
for (int i = 0; i < N_UPGRADE_SLOTS; i++) {
ItemStack stack = upgradesHandler.getStackInSlot(i);
if (stack.getItem() instanceof UpgradeItem upgradeItem) {
- upgradeCount.put(stack.getItem(), getUpgradeCount(stack.getItem()) + stack.getCount());
+ upgradeCount.put(upgradeItem, getUpgradeCount(stack.getItem()) + stack.getCount());
upgradeItem.onCompiled(stack, this);
}
}
@@ -605,7 +637,7 @@ public int getUpgradeCount(Item type) {
return upgradeCount.getOrDefault(type, 0);
}
- public Map
- getAllUpgrades() {
+ public Map getAllUpgrades() {
return Collections.unmodifiableMap(upgradeCount);
}
@@ -984,11 +1016,18 @@ class UpgradeHandler extends RouterItemHandler {
@Override
public boolean isItemValid(int slot, @Nonnull ItemStack stack) {
- // can't have the same upgrade in more than one slot
+ if (!super.isItemValid(slot, stack)) return false;
+ UpgradeItem item = (UpgradeItem) stack.getItem();
for (int i = 0; i < getSlots(); i++) {
- if (slot != i && stack.getItem() == getStackInSlot(i).getItem()) return false;
+ ItemStack inSlot = getStackInSlot(i);
+ if (inSlot.isEmpty() || slot == i) continue;
+ // can't have the same upgrade in more than one slot
+ // incompatible upgrades can't coexist
+ if (stack.getItem() == inSlot.getItem() || !((UpgradeItem) inSlot.getItem()).isCompatibleWith(item)) {
+ return false;
+ }
}
- return super.isItemValid(slot, stack);
+ return true;
}
@Override
diff --git a/src/main/java/me/desht/modularrouters/container/RouterMenu.java b/src/main/java/me/desht/modularrouters/container/RouterMenu.java
index 27b38f28..54537c82 100644
--- a/src/main/java/me/desht/modularrouters/container/RouterMenu.java
+++ b/src/main/java/me/desht/modularrouters/container/RouterMenu.java
@@ -1,5 +1,6 @@
package me.desht.modularrouters.container;
+import me.desht.modularrouters.api.event.RegisterRouterContainerData;
import me.desht.modularrouters.block.tile.ModularRouterBlockEntity;
import me.desht.modularrouters.core.ModBlockEntities;
import me.desht.modularrouters.core.ModMenuTypes;
@@ -12,9 +13,13 @@
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.phys.Vec3;
+import net.neoforged.neoforge.common.NeoForge;
import net.neoforged.neoforge.items.IItemHandler;
import net.neoforged.neoforge.items.SlotItemHandler;
+import java.util.Comparator;
+import java.util.Map;
+
import static me.desht.modularrouters.container.Layout.SLOT_X_SPACING;
import static me.desht.modularrouters.container.Layout.SLOT_Y_SPACING;
@@ -79,6 +84,12 @@ public RouterMenu(int windowId, Inventory invPlayer, BlockPos routerPos) {
}
addDataSlots(data);
+
+ final var event = new RegisterRouterContainerData(router);
+ NeoForge.EVENT_BUS.post(event);
+ event.getData().entrySet()
+ .stream().sorted(Map.Entry.comparingByKey())
+ .forEach(entry -> addDataSlot(entry.getValue()));
}
@Override
diff --git a/src/main/java/me/desht/modularrouters/item/upgrade/UpgradeItem.java b/src/main/java/me/desht/modularrouters/item/upgrade/UpgradeItem.java
index dc7386e1..49bf59ae 100644
--- a/src/main/java/me/desht/modularrouters/item/upgrade/UpgradeItem.java
+++ b/src/main/java/me/desht/modularrouters/item/upgrade/UpgradeItem.java
@@ -4,8 +4,11 @@
import me.desht.modularrouters.client.util.TintColor;
import me.desht.modularrouters.core.ModItems;
import me.desht.modularrouters.item.MRBaseItem;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.nbt.Tag;
import net.minecraft.network.chat.Component;
import net.minecraft.world.item.ItemStack;
+import org.jetbrains.annotations.Nullable;
import java.util.List;
@@ -19,14 +22,47 @@ public TintColor getItemTint() {
return TintColor.WHITE;
}
+ /**
+ * Called when the router's upgrades are "compiled".
+ * Can be used to update buffer capacities, for instance.
+ *
+ * @param stack the upgrade stack
+ * @param router the router
+ */
public void onCompiled(ItemStack stack, ModularRouterBlockEntity router) {
// no-op by default
}
+ /**
+ * {@return the tag that will be sent for clients, to sync data}
+ * @param router the router
+ */
+ @Nullable
+ public CompoundTag createUpdateTag(ModularRouterBlockEntity router) {
+ return null;
+ }
+
+ /**
+ * Process an update packet.
+ *
+ * @param router the router
+ * @param tag the update tag
+ */
+ public void processClientSync(ModularRouterBlockEntity router, @Nullable CompoundTag tag) {
+
+ }
+
protected void addExtraInformation(ItemStack stack, List list) {
}
+ /**
+ * {@return {@code true} if this module can coexist with the {@code other} upgrade}
+ */
+ public boolean isCompatibleWith(UpgradeItem other) {
+ return true;
+ }
+
/**
* Get the maximum number of this upgrade that can be put in an upgrade slot
* @param slot the slot number
diff --git a/src/main/java/me/desht/modularrouters/logic/ModuleTarget.java b/src/main/java/me/desht/modularrouters/logic/ModuleTarget.java
index fdd156be..bb886f3b 100644
--- a/src/main/java/me/desht/modularrouters/logic/ModuleTarget.java
+++ b/src/main/java/me/desht/modularrouters/logic/ModuleTarget.java
@@ -10,6 +10,7 @@
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.Level;
+import net.neoforged.neoforge.capabilities.BlockCapability;
import net.neoforged.neoforge.capabilities.BlockCapabilityCache;
import net.neoforged.neoforge.capabilities.Capabilities;
import net.neoforged.neoforge.energy.IEnergyStorage;
@@ -17,6 +18,8 @@
import net.neoforged.neoforge.items.IItemHandler;
import javax.annotation.Nullable;
+import java.util.IdentityHashMap;
+import java.util.Map;
import java.util.Objects;
import java.util.Optional;
@@ -32,6 +35,7 @@ public class ModuleTarget {
private BlockCapabilityCache itemCapCache;
private BlockCapabilityCache fluidCapCache;
private BlockCapabilityCache energyCapCache;
+ private final Map, BlockCapabilityCache, ?>> capabilityCache = new IdentityHashMap<>();
public ModuleTarget(GlobalPos gPos, Direction face, String blockTranslationKey) {
this.gPos = gPos;
@@ -128,6 +132,28 @@ public Optional getEnergyHandler() {
return Optional.ofNullable(energyCapCache.getCapability());
}
+ /**
+ * Get a cached capability of the module target.
+ *
+ * @param capability the capability
+ * @param context the capability context
+ * @return the capability
+ */
+ @Nullable
+ public T getCapability(BlockCapability capability, @Nullable C context) {
+ var cached = (BlockCapabilityCache)capabilityCache.get(capability);
+ if (cached != null && Objects.equals(cached.context(), context)) {
+ return cached.getCapability();
+ }
+ ServerLevel level = MiscUtil.getWorldForGlobalPos(gPos);
+ if (level == null) {
+ return null;
+ }
+ cached = BlockCapabilityCache.create(capability, level, gPos.pos(), context);
+ capabilityCache.put(capability, cached);
+ return cached.getCapability();
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
diff --git a/src/main/java/me/desht/modularrouters/logic/compiled/CompiledModule.java b/src/main/java/me/desht/modularrouters/logic/compiled/CompiledModule.java
index 1912c3a5..87aa876e 100644
--- a/src/main/java/me/desht/modularrouters/logic/compiled/CompiledModule.java
+++ b/src/main/java/me/desht/modularrouters/logic/compiled/CompiledModule.java
@@ -1,6 +1,7 @@
package me.desht.modularrouters.logic.compiled;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
+import me.desht.modularrouters.api.event.ExecuteModuleEvent;
import me.desht.modularrouters.block.tile.ModularRouterBlockEntity;
import me.desht.modularrouters.core.ModItems;
import me.desht.modularrouters.item.augment.AugmentItem.AugmentCounter;
@@ -22,6 +23,7 @@
import net.neoforged.neoforge.items.IItemHandler;
import net.neoforged.neoforge.items.ItemHandlerHelper;
import org.apache.commons.lang3.Validate;
+import org.jetbrains.annotations.ApiStatus;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -41,6 +43,8 @@ public abstract class CompiledModule {
private final int regulationAmount;
private final AugmentCounter augmentCounter;
private final int range, rangeSquared;
+ @Nullable
+ private final ExecuteModuleEvent event;
private int lastMatchPos = 0;
private final Map lastMatchPosMap = new Object2IntOpenHashMap<>();
@@ -53,7 +57,7 @@ public abstract class CompiledModule {
* @param router router the module is installed in, may be null for an uninstalled module
* @param stack item stack of the module item being compiled
*/
- CompiledModule(@Nullable ModularRouterBlockEntity router, ItemStack stack) {
+ protected CompiledModule(@Nullable ModularRouterBlockEntity router, ItemStack stack) {
Validate.isTrue(stack.getItem() instanceof ModuleItem, "expected module item, got " + stack);
module = (ModuleItem) stack.getItem();
@@ -69,6 +73,7 @@ public abstract class CompiledModule {
regulationAmount = ModuleHelper.getRegulatorAmount(stack);
facing = router == null ? null : router.getAbsoluteFacing(direction);
routerFacing = router == null ? null : router.getAbsoluteFacing(RelativeDirection.FRONT);
+ this.event = router == null ? null : new ExecuteModuleEvent(router, this);
}
/**
@@ -85,7 +90,7 @@ public Filter getFilter() {
return filter;
}
- public RelativeDirection getDirection() {
+ public final RelativeDirection getDirection() {
return direction;
}
@@ -100,7 +105,7 @@ protected boolean shouldStoreRawFilterItems() {
*
* @return the first target as set up by {@link #setupTargets(ModularRouterBlockEntity, ItemStack)}
*/
- ModuleTarget getTarget() {
+ public ModuleTarget getTarget() {
return targets == null || targets.isEmpty() ? null : targets.get(0);
}
@@ -110,7 +115,7 @@ ModuleTarget getTarget() {
*
* @return a list of the defined targets as set up by {@link #setupTargets(ModularRouterBlockEntity, ItemStack)}
*/
- List getTargets() {
+ public List getTargets() {
return targets;
}
@@ -120,7 +125,7 @@ public ModuleItem.Termination termination() {
return termination;
}
- RouterRedstoneBehaviour getRedstoneBehaviour() {
+ public final RouterRedstoneBehaviour getRedstoneBehaviour() {
return behaviour;
}
@@ -128,7 +133,7 @@ public int getRegulationAmount() {
return augmentCounter.getAugmentCount(ModItems.REGULATOR_AUGMENT.get()) > 0 ? regulationAmount : 0;
}
- int getAugmentCount(Item augmentType) {
+ public int getAugmentCount(Item augmentType) {
return augmentCounter.getAugmentCount(augmentType);
}
@@ -139,16 +144,18 @@ int getAugmentCount(Item augmentType) {
*
* @return absolute direction of the module
*/
- Direction getFacing() {
+ public final Direction getFacing() {
return facing;
}
+ @ApiStatus.OverrideOnly
public void onCompiled(ModularRouterBlockEntity router) {
if (behaviour == RouterRedstoneBehaviour.PULSE) {
router.setHasPulsedModules(true);
}
}
+ @ApiStatus.OverrideOnly
public void cleanup(ModularRouterBlockEntity router) {
// does nothing by default
}
@@ -198,7 +205,7 @@ List setupTargets(ModularRouterBlockEntity router, ItemStack stack
return Collections.singletonList(new ModuleTarget(gPos, facing.getOpposite(), blockName));
}
- int getItemsPerTick(ModularRouterBlockEntity router) {
+ public int getItemsPerTick(ModularRouterBlockEntity router) {
int n = augmentCounter.getAugmentCount(ModItems.STACK_AUGMENT.get());
return n > 0 ? Math.min(1 << n, 64) : router.getItemsPerTick();
}
@@ -212,7 +219,7 @@ int getItemsPerTick(ModularRouterBlockEntity router) {
* @param router the router
* @return items actually transferred
*/
- ItemStack transferToRouter(IItemHandler handler, @Nullable BlockPos key, ModularRouterBlockEntity router) {
+ public final ItemStack transferToRouter(IItemHandler handler, @Nullable BlockPos key, ModularRouterBlockEntity router) {
CountedItemStacks count = getRegulationAmount() > 0 ? new CountedItemStacks(handler) : null;
ItemStack wanted = findItemToPull(router, handler, key, getItemsPerTick(router), count);
@@ -277,21 +284,22 @@ public ModuleTarget getEffectiveTarget(ModularRouterBlockEntity router) {
return getTarget();
}
+ @ApiStatus.OverrideOnly
public boolean shouldRun(boolean powered, boolean pulsed) {
return getRedstoneBehaviour().shouldRun(powered, pulsed);
}
- boolean isRegulationOK(ModularRouterBlockEntity router, boolean inbound) {
+ public boolean isRegulationOK(ModularRouterBlockEntity router, boolean inbound) {
if (regulationAmount == 0) return true; // no regulation
int items = router.getBufferItemStack().getCount();
return inbound && regulationAmount > items || !inbound && regulationAmount < items;
}
- int getRange() {
+ public int getRange() {
return range;
}
- int getRangeSquared() {
+ public int getRangeSquared() {
return rangeSquared;
}
@@ -299,10 +307,11 @@ private int getRangeModifier() {
return getAugmentCount(ModItems.RANGE_UP_AUGMENT.get()) - getAugmentCount(ModItems.RANGE_DOWN_AUGMENT.get());
}
- Direction getRouterFacing() {
+ protected Direction getRouterFacing() {
return routerFacing;
}
+ @ApiStatus.OverrideOnly
public void onNeighbourChange(ModularRouterBlockEntity router) {
}
@@ -313,4 +322,17 @@ public int getEnergyCost() {
public boolean careAboutItemAttributes() {
return false;
}
+
+ /**
+ * {@return the module type}
+ */
+ public ModuleItem getModule() {
+ return module;
+ }
+
+ @Nullable
+ @ApiStatus.Internal
+ public ExecuteModuleEvent getEvent() {
+ return event;
+ }
}