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; + } }