From 5c5957c5d49141035eeff4e9d72b632bc21f919c Mon Sep 17 00:00:00 2001 From: Matyrobbrt Date: Thu, 21 Nov 2024 20:03:01 +0200 Subject: [PATCH 01/10] Rework targeted modules --- .../api/event/AddModuleTargetEvent.java | 8 +- .../client/gui/module/FluidModuleScreen.java | 6 +- .../client/render/area/IPositionProvider.java | 5 +- .../core/ModDataComponents.java | 6 +- .../item/module/DistributorModule.java | 4 +- .../item/module/EnergyDistributorModule.java | 8 +- .../item/module/FluidModule1.java | 24 +- .../item/module/FluidModule2.java | 60 +-- .../item/module/ITargetedModule.java | 117 ++++++ .../item/module/ModuleItem.java | 32 +- .../item/module/PlayerModule.java | 6 +- .../item/module/PullerModule2.java | 2 +- .../item/module/SenderModule2.java | 2 +- .../item/module/SenderModule3.java | 11 +- .../item/module/TargetValidation.java | 31 ++ .../item/module/TargetedModule.java | 376 ------------------ .../item/module/adapter/IItemAdapter.java | 35 ++ .../module/adapter/TargetedModuleAdapter.java | 192 +++++++++ .../modularrouters/logic/ModuleTarget.java | 6 + .../logic/ModuleTargetList.java | 11 +- .../compiled/CompiledDistributorModule.java | 4 +- .../CompiledEnergyDistributorModule.java | 13 +- ...dModule1.java => CompiledFluidModule.java} | 8 +- .../logic/compiled/CompiledFluidModule2.java | 20 - .../logic/compiled/CompiledModule.java | 29 +- .../logic/compiled/CompiledPullerModule1.java | 2 +- .../logic/compiled/CompiledPullerModule2.java | 17 - .../logic/compiled/CompiledSenderModule2.java | 15 +- .../logic/compiled/CompiledSenderModule3.java | 9 - 29 files changed, 488 insertions(+), 571 deletions(-) create mode 100644 src/main/java/me/desht/modularrouters/item/module/ITargetedModule.java create mode 100644 src/main/java/me/desht/modularrouters/item/module/TargetValidation.java delete mode 100644 src/main/java/me/desht/modularrouters/item/module/TargetedModule.java create mode 100644 src/main/java/me/desht/modularrouters/item/module/adapter/IItemAdapter.java create mode 100644 src/main/java/me/desht/modularrouters/item/module/adapter/TargetedModuleAdapter.java rename src/main/java/me/desht/modularrouters/logic/compiled/{CompiledFluidModule1.java => CompiledFluidModule.java} (97%) delete mode 100644 src/main/java/me/desht/modularrouters/logic/compiled/CompiledFluidModule2.java diff --git a/src/main/java/me/desht/modularrouters/api/event/AddModuleTargetEvent.java b/src/main/java/me/desht/modularrouters/api/event/AddModuleTargetEvent.java index 79e27388..52d04b1a 100644 --- a/src/main/java/me/desht/modularrouters/api/event/AddModuleTargetEvent.java +++ b/src/main/java/me/desht/modularrouters/api/event/AddModuleTargetEvent.java @@ -1,6 +1,6 @@ package me.desht.modularrouters.api.event; -import me.desht.modularrouters.item.module.TargetedModule; +import me.desht.modularrouters.item.module.ModuleItem; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.context.UseOnContext; import net.neoforged.bus.api.Event; @@ -12,12 +12,12 @@ * This event can be used to allow a module to target blocks it otherwise couldn't in conjunction with {@link ExecuteModuleEvent}. */ public final class AddModuleTargetEvent extends Event { - private final TargetedModule item; + private final ModuleItem item; private final UseOnContext context; private boolean valid; @ApiStatus.Internal - public AddModuleTargetEvent(TargetedModule item, UseOnContext context, boolean valid) { + public AddModuleTargetEvent(ModuleItem item, UseOnContext context, boolean valid) { this.item = item; this.context = context; this.valid = valid; @@ -26,7 +26,7 @@ public AddModuleTargetEvent(TargetedModule item, UseOnContext context, boolean v /** * {@return the module type} */ - public TargetedModule getModuleType() { + public ModuleItem getModuleType() { return item; } diff --git a/src/main/java/me/desht/modularrouters/client/gui/module/FluidModuleScreen.java b/src/main/java/me/desht/modularrouters/client/gui/module/FluidModuleScreen.java index 6e24e2f6..24a24942 100644 --- a/src/main/java/me/desht/modularrouters/client/gui/module/FluidModuleScreen.java +++ b/src/main/java/me/desht/modularrouters/client/gui/module/FluidModuleScreen.java @@ -10,7 +10,7 @@ import me.desht.modularrouters.container.ModuleMenu; import me.desht.modularrouters.core.ModBlocks; import me.desht.modularrouters.core.ModDataComponents; -import me.desht.modularrouters.logic.compiled.CompiledFluidModule1; +import me.desht.modularrouters.logic.compiled.CompiledFluidModule; import me.desht.modularrouters.logic.settings.TransferDirection; import net.minecraft.Util; import net.minecraft.client.gui.GuiGraphics; @@ -47,7 +47,7 @@ public FluidModuleScreen(ModuleMenu container, Inventory inv, Component displayN public void init() { super.init(); - CompiledFluidModule1 cfm = new CompiledFluidModule1(null, moduleItemStack); + CompiledFluidModule cfm = new CompiledFluidModule(null, moduleItemStack); int max = ConfigHolder.common.router.baseTickRate.get() * ConfigHolder.common.router.fluidMaxTransferRate.get(); maxTransferField = new IntegerTextField(font, leftPos + 152, topPos + 23, 34, 12, @@ -113,7 +113,7 @@ public void containerTick() { @Override protected ItemStack buildModifiedItemStack() { return Util.make(super.buildModifiedItemStack(), stack -> - stack.set(ModDataComponents.FLUID_SETTINGS, new CompiledFluidModule1.FluidModuleSettings( + stack.set(ModDataComponents.FLUID_SETTINGS, new CompiledFluidModule.FluidModuleSettings( maxTransferField.getIntValue(), fluidDirButton.getState(), forceEmptyButton.isToggled(), diff --git a/src/main/java/me/desht/modularrouters/client/render/area/IPositionProvider.java b/src/main/java/me/desht/modularrouters/client/render/area/IPositionProvider.java index 63f9bcbe..18c78d51 100644 --- a/src/main/java/me/desht/modularrouters/client/render/area/IPositionProvider.java +++ b/src/main/java/me/desht/modularrouters/client/render/area/IPositionProvider.java @@ -1,6 +1,6 @@ package me.desht.modularrouters.client.render.area; -import me.desht.modularrouters.item.module.TargetedModule; +import me.desht.modularrouters.item.module.ITargetedModule; import me.desht.modularrouters.logic.ModuleTarget; import net.minecraft.world.item.ItemStack; @@ -19,8 +19,7 @@ public interface IPositionProvider { * @return a list of block positions that has been retrieved from the itemstack */ default List getStoredPositions(@Nonnull ItemStack stack) { - ModuleTarget target = TargetedModule.getTarget(stack); - return target == null ? List.of() : List.of(target); + return List.copyOf(ITargetedModule.getTargets(stack, false)); } /** diff --git a/src/main/java/me/desht/modularrouters/core/ModDataComponents.java b/src/main/java/me/desht/modularrouters/core/ModDataComponents.java index 8c395ba3..9f016e49 100644 --- a/src/main/java/me/desht/modularrouters/core/ModDataComponents.java +++ b/src/main/java/me/desht/modularrouters/core/ModDataComponents.java @@ -64,10 +64,10 @@ public class ModDataComponents { .persistent(CompiledFlingerModule.FlingerSettings.CODEC) .networkSynchronized(CompiledFlingerModule.FlingerSettings.STREAM_CODEC) ); - public static final Supplier> FLUID_SETTINGS + public static final Supplier> FLUID_SETTINGS = COMPONENTS.registerComponentType("fluid_settings", builder -> builder - .persistent(CompiledFluidModule1.FluidModuleSettings.CODEC) - .networkSynchronized(CompiledFluidModule1.FluidModuleSettings.STREAM_CODEC) + .persistent(CompiledFluidModule.FluidModuleSettings.CODEC) + .networkSynchronized(CompiledFluidModule.FluidModuleSettings.STREAM_CODEC) ); public static final Supplier> PLAYER_SETTINGS = COMPONENTS.registerComponentType("player_settings", builder -> builder diff --git a/src/main/java/me/desht/modularrouters/item/module/DistributorModule.java b/src/main/java/me/desht/modularrouters/item/module/DistributorModule.java index 31a71fd2..7d10ffec 100644 --- a/src/main/java/me/desht/modularrouters/item/module/DistributorModule.java +++ b/src/main/java/me/desht/modularrouters/item/module/DistributorModule.java @@ -46,7 +46,7 @@ public MenuType getMenuType() { @Override public List getStoredPositions(@Nonnull ItemStack stack) { - return ImmutableList.copyOf(TargetedModule.getTargets(stack, false)); + return ImmutableList.copyOf(ITargetedModule.getTargets(stack, false)); } @Override @@ -55,7 +55,7 @@ public TintColor getItemTint() { } @Override - protected int getMaxTargets() { + public int getMaxTargets() { return 8; } diff --git a/src/main/java/me/desht/modularrouters/item/module/EnergyDistributorModule.java b/src/main/java/me/desht/modularrouters/item/module/EnergyDistributorModule.java index e6ba3615..e71aa8cc 100644 --- a/src/main/java/me/desht/modularrouters/item/module/EnergyDistributorModule.java +++ b/src/main/java/me/desht/modularrouters/item/module/EnergyDistributorModule.java @@ -14,7 +14,7 @@ import javax.annotation.Nonnull; import java.util.List; -public class EnergyDistributorModule extends TargetedModule implements IRangedModule, IPositionProvider { +public class EnergyDistributorModule extends ModuleItem implements IRangedModule, IPositionProvider, ITargetedModule { private static final TintColor TINT_COLOR = new TintColor(79, 9, 90); public EnergyDistributorModule() { @@ -44,16 +44,16 @@ public int getHardMaxRange() { @Override public List getStoredPositions(@Nonnull ItemStack stack) { - return ImmutableList.copyOf(TargetedModule.getTargets(stack, false)); + return ImmutableList.copyOf(ITargetedModule.getTargets(stack, false)); } @Override - protected boolean isValidTarget(UseOnContext ctx) { + public boolean isValidTarget(UseOnContext ctx) { return ctx.getLevel().getCapability(Capabilities.EnergyStorage.BLOCK, ctx.getClickedPos(), ctx.getClickedFace()) != null; } @Override - protected int getMaxTargets() { + public int getMaxTargets() { return 8; } diff --git a/src/main/java/me/desht/modularrouters/item/module/FluidModule1.java b/src/main/java/me/desht/modularrouters/item/module/FluidModule1.java index f7893863..98aa1180 100644 --- a/src/main/java/me/desht/modularrouters/item/module/FluidModule1.java +++ b/src/main/java/me/desht/modularrouters/item/module/FluidModule1.java @@ -1,5 +1,6 @@ package me.desht.modularrouters.item.module; +import me.desht.modularrouters.api.matching.IItemMatcher; import me.desht.modularrouters.client.util.TintColor; import me.desht.modularrouters.config.ConfigHolder; import me.desht.modularrouters.container.ModuleMenu; @@ -7,10 +8,9 @@ import me.desht.modularrouters.core.ModItems; import me.desht.modularrouters.core.ModMenuTypes; import me.desht.modularrouters.item.smartfilter.SmartFilterItem; -import me.desht.modularrouters.logic.compiled.CompiledFluidModule1; -import me.desht.modularrouters.logic.compiled.CompiledFluidModule1.FluidModuleSettings; +import me.desht.modularrouters.logic.compiled.CompiledFluidModule; +import me.desht.modularrouters.logic.compiled.CompiledFluidModule.FluidModuleSettings; import me.desht.modularrouters.logic.filter.matchers.FluidMatcher; -import me.desht.modularrouters.api.matching.IItemMatcher; import net.minecraft.ChatFormatting; import net.minecraft.network.chat.Component; import net.minecraft.world.inventory.MenuType; @@ -27,7 +27,7 @@ public class FluidModule1 extends ModuleItem { private static final TintColor TINT_COLOR = new TintColor(79, 191, 255); public FluidModule1() { - super(ModItems.defaultProps(), CompiledFluidModule1::new); + super(ModItems.defaultProps(), CompiledFluidModule::new); } @Override @@ -50,7 +50,11 @@ protected Component getFilterItemDisplayName(ItemStack stack) { protected void addExtraInformation(ItemStack stack, List list) { super.addExtraInformation(stack, list); - addFluidModuleInformation(stack, list); + FluidModuleSettings settings = stack.getOrDefault(ModDataComponents.FLUID_SETTINGS.get(), FluidModuleSettings.DEFAULT); + list.add(xlate("modularrouters.itemText.transfer_direction", + xlate(settings.direction().getTranslationKey()).withStyle(ChatFormatting.AQUA)).withStyle(ChatFormatting.YELLOW)); + list.add(xlate("modularrouters.itemText.fluid.maxTransfer", + colorText(settings.maxTransfer(), ChatFormatting.AQUA)).withStyle(ChatFormatting.YELLOW)); } @Override @@ -81,14 +85,4 @@ public boolean isFluidModule() { public TintColor getItemTint() { return TINT_COLOR; } - - static void addFluidModuleInformation(ItemStack stack, List list) { -// CompiledFluidModule1 cfm = new CompiledFluidModule1(null, stack); - FluidModuleSettings settings = stack.getOrDefault(ModDataComponents.FLUID_SETTINGS.get(), FluidModuleSettings.DEFAULT); - list.add(xlate("modularrouters.itemText.transfer_direction", - xlate(settings.direction().getTranslationKey()).withStyle(ChatFormatting.AQUA)).withStyle(ChatFormatting.YELLOW)); - list.add(xlate("modularrouters.itemText.fluid.maxTransfer", - colorText(settings.maxTransfer(), ChatFormatting.AQUA)).withStyle(ChatFormatting.YELLOW)); - } - } diff --git a/src/main/java/me/desht/modularrouters/item/module/FluidModule2.java b/src/main/java/me/desht/modularrouters/item/module/FluidModule2.java index bcf34353..cdd9d03b 100644 --- a/src/main/java/me/desht/modularrouters/item/module/FluidModule2.java +++ b/src/main/java/me/desht/modularrouters/item/module/FluidModule2.java @@ -3,34 +3,14 @@ import me.desht.modularrouters.client.render.area.IPositionProvider; import me.desht.modularrouters.client.util.TintColor; import me.desht.modularrouters.config.ConfigHolder; -import me.desht.modularrouters.container.ModuleMenu; -import me.desht.modularrouters.core.ModDataComponents; -import me.desht.modularrouters.core.ModItems; -import me.desht.modularrouters.core.ModMenuTypes; -import me.desht.modularrouters.item.smartfilter.SmartFilterItem; -import me.desht.modularrouters.logic.ModuleTargetList; -import me.desht.modularrouters.logic.compiled.CompiledFluidModule2; -import me.desht.modularrouters.logic.filter.matchers.FluidMatcher; -import me.desht.modularrouters.api.matching.IItemMatcher; -import net.minecraft.network.chat.Component; -import net.minecraft.world.inventory.MenuType; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.context.UseOnContext; -import net.neoforged.neoforge.fluids.FluidStack; -import net.neoforged.neoforge.fluids.FluidUtil; -import java.util.List; - -public class FluidModule2 extends TargetedModule implements IRangedModule, IPositionProvider { +public class FluidModule2 extends FluidModule1 implements IRangedModule, IPositionProvider, ITargetedModule { private static final TintColor TINT_COLOR = new TintColor(64, 224, 255); - public FluidModule2() { - super(ModItems.moduleProps() - .component(ModDataComponents.MODULE_TARGET_LIST, ModuleTargetList.EMPTY), CompiledFluidModule2::new); - } - @Override - protected boolean isValidTarget(UseOnContext ctx) { + public boolean isValidTarget(UseOnContext ctx) { return !ctx.getLevel().isEmptyBlock(ctx.getClickedPos()); } @@ -38,36 +18,6 @@ protected boolean isValidTarget(UseOnContext ctx) { public boolean isDirectional() { return false; } - - @Override - public MenuType getMenuType() { - return ModMenuTypes.FLUID_MENU.get(); - } - - @Override - protected Component getFilterItemDisplayName(ItemStack stack) { - return FluidUtil.getFluidContained(stack).map(FluidStack::getHoverName).orElse(stack.getHoverName()); - } - - @Override - public boolean isItemValidForFilter(ItemStack stack) { - // only fluid-holding items or a smart filter item can go into a fluid module's filter - if (stack.isEmpty() || stack.getItem() instanceof SmartFilterItem) return true; - if (stack.getCount() > 1) return false; - - return FluidUtil.getFluidContained(stack).map(fluidStack -> !fluidStack.isEmpty()).orElse(false); - } - - @Override - public IItemMatcher getFilterItemMatcher(ItemStack stack) { - return new FluidMatcher(stack); - } - - @Override - public boolean isFluidModule() { - return true; - } - @Override public int getBaseRange() { return ConfigHolder.common.module.fluid2BaseRange.get(); @@ -83,12 +33,6 @@ public TintColor getItemTint() { return TINT_COLOR; } - @Override - protected void addExtraInformation(ItemStack stack, List list) { - super.addExtraInformation(stack, list); - FluidModule1.addFluidModuleInformation(stack, list); - } - @Override public int getRenderColor(int index) { return 0x8040E0FF; diff --git a/src/main/java/me/desht/modularrouters/item/module/ITargetedModule.java b/src/main/java/me/desht/modularrouters/item/module/ITargetedModule.java new file mode 100644 index 00000000..68e09ee4 --- /dev/null +++ b/src/main/java/me/desht/modularrouters/item/module/ITargetedModule.java @@ -0,0 +1,117 @@ +package me.desht.modularrouters.item.module; + +import com.google.common.collect.Sets; +import me.desht.modularrouters.api.event.AddModuleTargetEvent; +import me.desht.modularrouters.core.ModDataComponents; +import me.desht.modularrouters.logic.ModuleTarget; +import me.desht.modularrouters.logic.ModuleTargetList; +import me.desht.modularrouters.util.BlockUtil; +import me.desht.modularrouters.util.InventoryUtils; +import me.desht.modularrouters.util.MiscUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.context.UseOnContext; +import net.minecraft.world.level.Level; +import net.neoforged.neoforge.common.NeoForge; +import org.jetbrains.annotations.ApiStatus; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + +public interface ITargetedModule { + /** + * Override-only method that checks whether this module can have the block of the context selected. + *

Note: it is not guaranteed that the module will only have blocks that pass this test selected, see {@link AddModuleTargetEvent}. + */ + @ApiStatus.OverrideOnly + default boolean isValidTarget(UseOnContext ctx) { + return InventoryUtils.getInventory(ctx.getLevel(), ctx.getClickedPos(), ctx.getClickedFace()).isPresent(); + } + + default int getMaxTargets() { + return 1; + } + + /** + * Does this module have limited range? + * + * @return true if range is limited, false otherwise + */ + default boolean isRangeLimited() { + return true; + } + + /** + * {@return whether this module can operate in the given {@code dimension}} + */ + default boolean canOperateInDimension(ResourceKey dimension) { + return true; + } + + /** + * Retrieve multi-targeting information from a module itemstack. + * + * @param stack the module item stack + * @param checkBlockName verify the name of the target block - only works server-side + * @implNote the set will be limited to the max target amount of the module + * @return a set of targets for the module + */ + static Set getTargets(ItemStack stack, boolean checkBlockName) { + var max = ((ITargetedModule) stack.getItem()).getMaxTargets(); + Set result = Sets.newHashSet(); + + boolean update = false; + var targets = stack.getOrDefault(ModDataComponents.MODULE_TARGET_LIST, ModuleTargetList.EMPTY).targets(); + for (int i = 0; i < targets.size() && result.size() < max; i++) { + var target = targets.get(i); + if (checkBlockName) { + var newTarget = updateTargetBlockName(stack, target); + if (newTarget != target) update = true; + target = newTarget; + } + if (target != null) { + result.add(target); + } + } + + if (update) { + stack.set(ModDataComponents.MODULE_TARGET_LIST, new ModuleTargetList(List.copyOf(result))); + } + + return result; + } + + /** + * Sets the targets of a module. + * @param stack the module stack to update + * @param targets the new targets + */ + static void setTargets(ItemStack stack, Collection targets) { + stack.set(ModDataComponents.MODULE_TARGET_LIST, new ModuleTargetList(List.copyOf(targets))); + } + + /** + * Checks if the module can select the target of the {@code context}. + */ + static boolean canSelectTarget(UseOnContext context) { + var module = context.getItemInHand().getItem(); + return NeoForge.EVENT_BUS.post(new AddModuleTargetEvent((ModuleItem) module, context, ((ITargetedModule) module).isValidTarget(context))).isValid(); + } + + private static ModuleTarget updateTargetBlockName(ItemStack stack, ModuleTarget target) { + ServerLevel level = MiscUtil.getWorldForGlobalPos(target.gPos); + BlockPos pos = target.gPos.pos(); + if (level != null && level.getChunkSource().hasChunk(pos.getX() >> 4, pos.getZ() >> 4)) { + String invName = BlockUtil.getBlockName(level, pos); + if (!target.blockTranslationKey.equals(invName)) { + return new ModuleTarget(target.gPos, target.face, invName); + } else { + return target; + } + } + return null; + } +} diff --git a/src/main/java/me/desht/modularrouters/item/module/ModuleItem.java b/src/main/java/me/desht/modularrouters/item/module/ModuleItem.java index 6d1fd168..255a2d36 100644 --- a/src/main/java/me/desht/modularrouters/item/module/ModuleItem.java +++ b/src/main/java/me/desht/modularrouters/item/module/ModuleItem.java @@ -2,6 +2,7 @@ import com.google.common.collect.Lists; import me.desht.modularrouters.api.MRCapabilities; +import me.desht.modularrouters.api.matching.IItemMatcher; import me.desht.modularrouters.block.tile.ModularRouterBlockEntity; import me.desht.modularrouters.client.ClientSetup; import me.desht.modularrouters.client.util.ClientUtil; @@ -15,11 +16,16 @@ import me.desht.modularrouters.item.MRBaseItem; import me.desht.modularrouters.item.augment.AugmentItem; import me.desht.modularrouters.item.augment.AugmentItem.AugmentCounter; +import me.desht.modularrouters.item.module.adapter.IItemAdapter; +import me.desht.modularrouters.item.module.adapter.TargetedModuleAdapter; import me.desht.modularrouters.item.smartfilter.SmartFilterItem; import me.desht.modularrouters.logic.compiled.CompiledModule; -import me.desht.modularrouters.api.matching.IItemMatcher; import me.desht.modularrouters.logic.filter.matchers.SimpleItemMatcher; -import me.desht.modularrouters.logic.settings.*; +import me.desht.modularrouters.logic.settings.ModuleFlags; +import me.desht.modularrouters.logic.settings.ModuleSettings; +import me.desht.modularrouters.logic.settings.ModuleTermination; +import me.desht.modularrouters.logic.settings.RedstoneBehaviour; +import me.desht.modularrouters.logic.settings.RelativeDirection; import me.desht.modularrouters.util.MFLocator; import net.minecraft.ChatFormatting; import net.minecraft.core.component.DataComponents; @@ -37,6 +43,7 @@ import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.TooltipFlag; +import net.minecraft.world.item.context.UseOnContext; import net.minecraft.world.item.enchantment.Enchantment; import net.minecraft.world.item.enchantment.EnchantmentHelper; import net.minecraft.world.item.enchantment.ItemEnchantments; @@ -53,10 +60,13 @@ public abstract class ModuleItem extends MRBaseItem implements ModItems.ITintable { private final BiFunction compiler; + private final IItemAdapter adapter; public ModuleItem(Properties props, BiFunction compiler) { super(props); this.compiler = compiler; + + this.adapter = this instanceof ITargetedModule tm ? new TargetedModuleAdapter(tm) : new IItemAdapter.NoOp(); } public static ModuleSettings getCommonSettings(ItemStack moduleStack) { @@ -144,6 +154,12 @@ public void appendHoverText(ItemStack stack, Item.TooltipContext context, List list) { + super.addUsageInformation(itemstack, list); + adapter.addUsageInformation(itemstack, list); + } + @Override protected void addExtraInformation(ItemStack stack, List list) { addSettingsInformation(stack, list); @@ -207,6 +223,8 @@ protected void addSettingsInformation(ItemStack stack, List list) { if (energy != 0) { list.add(xlate("modularrouters.itemText.misc.energyUsage", colorText(energy, ChatFormatting.AQUA)).withStyle(ChatFormatting.YELLOW)); } + + adapter.addSettingsInformation(stack, list); } public abstract int getEnergyCost(ItemStack stack); @@ -290,6 +308,13 @@ public InteractionResultHolder use(Level world, Player player, Intera return new InteractionResultHolder<>(InteractionResult.SUCCESS, stack); } + @Override + public InteractionResult useOn(UseOnContext ctx) { + var res = adapter.useOn(ctx); + if (res != InteractionResult.PASS) return res; + return super.useOn(ctx); + } + @Override public boolean isFoil(ItemStack stack) { if (stack.getItem() instanceof IPickaxeUser pickaxeUser) { @@ -304,7 +329,7 @@ public String getRegulatorTranslationKey(ItemStack stack) { } public InteractionResultHolder onSneakRightClick(ItemStack stack, Level world, Player player, InteractionHand hand) { - return new InteractionResultHolder<>(InteractionResult.PASS, stack); + return adapter.onSneakRightClick(stack, world, player, hand); } /** @@ -314,6 +339,7 @@ public InteractionResultHolder onSneakRightClick(ItemStack stack, Lev * @param player the player holding the module */ public void doModuleValidation(ItemStack stack, ServerPlayer player) { + adapter.doModuleValidation(stack, player); } public static class ModuleMenuProvider implements MenuProvider { diff --git a/src/main/java/me/desht/modularrouters/item/module/PlayerModule.java b/src/main/java/me/desht/modularrouters/item/module/PlayerModule.java index ff7ad78c..28735898 100644 --- a/src/main/java/me/desht/modularrouters/item/module/PlayerModule.java +++ b/src/main/java/me/desht/modularrouters/item/module/PlayerModule.java @@ -91,9 +91,9 @@ public int getEnergyCost(ItemStack stack) { @Override public void doModuleValidation(ItemStack stack, ServerPlayer player) { - TargetedModule.TargetValidation v = ModularRouters.getDimensionBlacklist().test(player.level().dimension().location()) ? - TargetedModule.TargetValidation.BAD_DIMENSION : - TargetedModule.TargetValidation.OK; + TargetValidation v = ModularRouters.getDimensionBlacklist().test(player.level().dimension().location()) ? + TargetValidation.BAD_DIMENSION : + TargetValidation.OK; MutableComponent msg = Component.translatable(v.translationKey()).withStyle(v.getColor()); player.displayClientMessage(msg, false); } diff --git a/src/main/java/me/desht/modularrouters/item/module/PullerModule2.java b/src/main/java/me/desht/modularrouters/item/module/PullerModule2.java index 0f7cdf4e..3967f8e1 100644 --- a/src/main/java/me/desht/modularrouters/item/module/PullerModule2.java +++ b/src/main/java/me/desht/modularrouters/item/module/PullerModule2.java @@ -7,7 +7,7 @@ import me.desht.modularrouters.logic.compiled.CompiledPullerModule2; import net.minecraft.world.item.ItemStack; -public class PullerModule2 extends TargetedModule implements IRangedModule, IPositionProvider { +public class PullerModule2 extends ModuleItem implements IRangedModule, IPositionProvider, ITargetedModule { private static final TintColor TINT_COLOR = new TintColor(128, 128, 255); public PullerModule2() { diff --git a/src/main/java/me/desht/modularrouters/item/module/SenderModule2.java b/src/main/java/me/desht/modularrouters/item/module/SenderModule2.java index b80d05bb..88b6d03e 100644 --- a/src/main/java/me/desht/modularrouters/item/module/SenderModule2.java +++ b/src/main/java/me/desht/modularrouters/item/module/SenderModule2.java @@ -12,7 +12,7 @@ import java.util.function.BiFunction; -public class SenderModule2 extends TargetedModule implements IRangedModule, IPositionProvider { +public class SenderModule2 extends ModuleItem implements IRangedModule, IPositionProvider, ITargetedModule { private static final TintColor TINT_COLOR = new TintColor(149, 255, 93); public SenderModule2() { diff --git a/src/main/java/me/desht/modularrouters/item/module/SenderModule3.java b/src/main/java/me/desht/modularrouters/item/module/SenderModule3.java index 24d23120..0c49d931 100644 --- a/src/main/java/me/desht/modularrouters/item/module/SenderModule3.java +++ b/src/main/java/me/desht/modularrouters/item/module/SenderModule3.java @@ -6,10 +6,11 @@ import me.desht.modularrouters.config.ConfigHolder; import me.desht.modularrouters.core.ModItems; import me.desht.modularrouters.logic.compiled.CompiledSenderModule3; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.ResourceKey; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; -public class SenderModule3 extends TargetedModule implements IPositionProvider { +public class SenderModule3 extends ModuleItem implements IPositionProvider, ITargetedModule { private static final TintColor TINT_COLOR = new TintColor(25, 255, 11); public SenderModule3() { @@ -22,7 +23,7 @@ public boolean isDirectional() { } @Override - protected boolean isRangeLimited() { + public boolean isRangeLimited() { return false; } @@ -42,7 +43,7 @@ public int getEnergyCost(ItemStack stack) { } @Override - protected boolean badDimension(ResourceLocation dimId) { - return ModularRouters.getDimensionBlacklist().test(dimId); + public boolean canOperateInDimension(ResourceKey dimension) { + return ModularRouters.getDimensionBlacklist().test(dimension.location()); } } diff --git a/src/main/java/me/desht/modularrouters/item/module/TargetValidation.java b/src/main/java/me/desht/modularrouters/item/module/TargetValidation.java new file mode 100644 index 00000000..2b5efffa --- /dev/null +++ b/src/main/java/me/desht/modularrouters/item/module/TargetValidation.java @@ -0,0 +1,31 @@ +package me.desht.modularrouters.item.module; + +import net.minecraft.ChatFormatting; +import net.minecraft.util.StringRepresentable; + +public enum TargetValidation implements StringRepresentable { + OK("ok"), + OUT_OF_RANGE("out_of_range"), + NOT_LOADED("not_loaded"), + NOT_INVENTORY("no_inventory"), + BAD_DIMENSION("bad_dimension"); + + private final String name; + + TargetValidation(String name) { + this.name = name; + } + + public ChatFormatting getColor() { + return this == OK ? ChatFormatting.GREEN : ChatFormatting.RED; + } + + public String translationKey() { + return "modularrouters.chatText.targetValidation." + getSerializedName(); + } + + @Override + public String getSerializedName() { + return name; + } +} diff --git a/src/main/java/me/desht/modularrouters/item/module/TargetedModule.java b/src/main/java/me/desht/modularrouters/item/module/TargetedModule.java deleted file mode 100644 index 9557626d..00000000 --- a/src/main/java/me/desht/modularrouters/item/module/TargetedModule.java +++ /dev/null @@ -1,376 +0,0 @@ -package me.desht.modularrouters.item.module; - -import com.google.common.collect.Sets; -import me.desht.modularrouters.ModularRouters; -import me.desht.modularrouters.api.event.AddModuleTargetEvent; -import me.desht.modularrouters.block.tile.ModularRouterBlockEntity; -import me.desht.modularrouters.client.util.ClientUtil; -import me.desht.modularrouters.config.ConfigHolder; -import me.desht.modularrouters.core.ModDataComponents; -import me.desht.modularrouters.core.ModSounds; -import me.desht.modularrouters.logic.ModuleTarget; -import me.desht.modularrouters.logic.ModuleTargetList; -import me.desht.modularrouters.logic.compiled.CompiledModule; -import me.desht.modularrouters.util.BlockUtil; -import me.desht.modularrouters.util.InventoryUtils; -import me.desht.modularrouters.util.MiscUtil; -import net.minecraft.ChatFormatting; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.core.GlobalPos; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.MutableComponent; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.sounds.SoundSource; -import net.minecraft.util.StringRepresentable; -import net.minecraft.world.InteractionHand; -import net.minecraft.world.InteractionResult; -import net.minecraft.world.InteractionResultHolder; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.Item; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.context.UseOnContext; -import net.minecraft.world.level.Level; -import net.neoforged.neoforge.common.NeoForge; -import org.jetbrains.annotations.ApiStatus; - -import java.util.List; -import java.util.Set; -import java.util.function.BiFunction; - -import static me.desht.modularrouters.client.util.ClientUtil.xlate; -import static me.desht.modularrouters.util.MiscUtil.asMutableComponent; - -/** - * Represents a module with a specific target block or blocks (stored in "modularrouters:module_target_list" component). - */ -public abstract class TargetedModule extends ModuleItem { - protected TargetedModule(Item.Properties props, BiFunction compiler) { - super(props.component(ModDataComponents.MODULE_TARGET_LIST, ModuleTargetList.EMPTY), compiler); - } - - @Override - public InteractionResult useOn(UseOnContext ctx) { - if (ctx.getPlayer() != null && ctx.getPlayer().isShiftKeyDown()) { - if (getMaxTargets() == 1) { - if (canSelectTarget(ctx)) { - handleSingleTarget(ctx.getItemInHand(), ctx.getPlayer(), ctx.getLevel(), ctx.getClickedPos(), ctx.getClickedFace()); - return InteractionResult.SUCCESS; - } - } else { - var res = handleMultiTarget(ctx.getItemInHand(), ctx, ctx.getPlayer(), ctx.getLevel(), ctx.getClickedPos(), ctx.getClickedFace()); - if (res == InteractionResult.PASS) return res; - } - return super.useOn(ctx); - } else { - return InteractionResult.PASS; - } - } - - /** - * Override-only method that checks whether this module can have the block of the context selected. - *

Note: it is not guaranteed that the module will only have blocks that pass this test selected, see {@link AddModuleTargetEvent}. - */ - @ApiStatus.OverrideOnly - protected boolean isValidTarget(UseOnContext ctx) { - return InventoryUtils.getInventory(ctx.getLevel(), ctx.getClickedPos(), ctx.getClickedFace()).isPresent(); - } - - /** - * Checks if the module can select the target of the {@code context}. - */ - public boolean canSelectTarget(UseOnContext context) { - return NeoForge.EVENT_BUS.post(new AddModuleTargetEvent(this, context, isValidTarget(context))).isValid(); - } - - private void handleSingleTarget(ItemStack stack, Player player, Level world, BlockPos pos, Direction face) { - if (!world.isClientSide) { - setTarget(stack, world, pos, face); - ModuleTarget tgt = getTarget(stack, true); - if (tgt != null) { - MutableComponent msg = Component.translatable("modularrouters.chatText.misc.targetSet").append(tgt.getTextComponent()); - player.displayClientMessage(msg.withStyle(ChatFormatting.YELLOW), true); - world.playSound(null, pos, ModSounds.SUCCESS.get(), SoundSource.BLOCKS, - ConfigHolder.common.sound.bleepVolume.get().floatValue(), 1.3f); - } - } - } - - private InteractionResult handleMultiTarget(ItemStack stack, UseOnContext context, Player player, Level world, BlockPos pos, Direction face) { - Set targets = getTargets(stack, !world.isClientSide); - String invName = BlockUtil.getBlockName(world, pos); - GlobalPos gPos = MiscUtil.makeGlobalPos(world, pos); - ModuleTarget tgt = new ModuleTarget(gPos, face, invName); - - // Allow removing targets without checking if they're valid - if (targets.contains(tgt)) { - if (world.isClientSide) return InteractionResult.SUCCESS; - targets.remove(tgt); - - player.displayClientMessage(Component.translatable("modularrouters.chatText.misc.targetRemoved", targets.size(), getMaxTargets()) - .append(tgt.getTextComponent()).withStyle(ChatFormatting.YELLOW), true); - world.playSound(null, pos, ModSounds.SUCCESS.get(), SoundSource.BLOCKS, ConfigHolder.common.sound.bleepVolume.get().floatValue(), 1.1f); - setTargetList(stack, targets); - return InteractionResult.SUCCESS; - } - - if (canSelectTarget(context)) { - if (world.isClientSide) return InteractionResult.SUCCESS; - if (targets.size() < getMaxTargets()) { - targets.add(tgt); - player.displayClientMessage(Component.translatable("modularrouters.chatText.misc.targetAdded", targets.size(), getMaxTargets()) - .append(tgt.getTextComponent()).withStyle(ChatFormatting.YELLOW), true); - - world.playSound(null, pos, ModSounds.SUCCESS.get(), SoundSource.BLOCKS, - ConfigHolder.common.sound.bleepVolume.get().floatValue(), 1.3f); - setTargetList(stack, targets); - } else { - // too many targets already - player.displayClientMessage(Component.translatable("modularrouters.chatText.misc.tooManyTargets", getMaxTargets()) - .withStyle(ChatFormatting.RED), true); - world.playSound(null, pos, ModSounds.ERROR.get(), SoundSource.BLOCKS, 1.0f, 1.3f); - } - - return InteractionResult.SUCCESS; - } - return InteractionResult.PASS; - } - - - @Override - public void addUsageInformation(ItemStack itemstack, List list) { - super.addUsageInformation(itemstack, list); - list.add(xlate(getMaxTargets() > 1 ? "modularrouters.itemText.targetingHintMulti" : "modularrouters.itemText.targetingHint").withStyle(ChatFormatting.YELLOW)); - } - - @Override - protected void addSettingsInformation(ItemStack stack, List list) { - super.addSettingsInformation(stack, list); - - Set targets; - - if (getMaxTargets() > 1) { - targets = getTargets(stack, false); - } else { - targets = Sets.newHashSet(getTarget(stack)); - } - - for (ModuleTarget target : targets) { - if (target != null) { - Component msg = Component.literal("▶ ").append(asMutableComponent(target.getTextComponent()).withStyle(ChatFormatting.WHITE)); - list.add(msg); - ClientUtil.getOpenItemRouter().ifPresent(router -> { - ModuleTarget moduleTarget = new ModuleTarget(router.getGlobalPos()); - TargetValidation val = validateTarget(stack, moduleTarget, target, false); - if (val != TargetValidation.OK) { - list.add(xlate(val.translationKey()).withStyle(val.getColor())); - } - }); - } - } - } - - @Override - public InteractionResultHolder onSneakRightClick(ItemStack stack, Level world, Player player, InteractionHand hand) { - if (!world.isClientSide && getTarget(stack) != null && getMaxTargets() == 1) { - setTarget(stack, world, null, null); - world.playSound(null, player.blockPosition(), ModSounds.SUCCESS.get(), SoundSource.BLOCKS, - ConfigHolder.common.sound.bleepVolume.get().floatValue(), 1.1f); - player.displayClientMessage(Component.translatable("modularrouters.chatText.misc.targetCleared").withStyle(ChatFormatting.YELLOW), true); - } - return new InteractionResultHolder<>(InteractionResult.SUCCESS, stack); - } - - /** - * Put information about the target into the module item's NBT. This needs to be done server-side! - * - * @param stack the module item - * @param world the world the target is in - * @param pos the position of the target - * @param face clicked face of the target - */ - private static void setTarget(ItemStack stack, Level world, BlockPos pos, Direction face) { - if (world.isClientSide) { - ModularRouters.LOGGER.warn("TargetModule.setTarget() should not be called client-side!"); - return; - } - - if (pos == null) { - stack.set(ModDataComponents.MODULE_TARGET_LIST, ModuleTargetList.EMPTY); - } else { - ModuleTarget mt = new ModuleTarget(MiscUtil.makeGlobalPos(world, pos), face, BlockUtil.getBlockName(world, pos)); - stack.set(ModDataComponents.MODULE_TARGET_LIST, ModuleTargetList.singleTarget(mt)); - } - } - - /** - * Retrieve targeting information from a module itemstack. Can be called server or client-side. - * - * @param stack the module item stack - * @return targeting data - */ - public static ModuleTarget getTarget(ItemStack stack) { - return getTarget(stack, false); - } - - /** - * Retrieve targeting information from a module itemstack. Can be called server or client-side; if called - * server-side, it will also revalidate the name of the target block if the checkName parameter is true. - * - * @param stack the module item stack - * @param checkBlockName verify the name of the target block - only works server-side - * @return targeting data - */ - public static ModuleTarget getTarget(ItemStack stack, boolean checkBlockName) { - ModuleTargetList targetList = stack.getOrDefault(ModDataComponents.MODULE_TARGET_LIST, ModuleTargetList.EMPTY); - - if (!targetList.isEmpty()) { - return checkBlockName ? updateTargetBlockName(stack, targetList.getSingle()) : targetList.getSingle(); - } - - return null; - } - - /** - * Retrieve multi-targeting information from a module itemstack. - * - * @param stack the module item stack - * @param checkBlockName verify the name of the target block - only works server-side - * @return a set of targets for the module - */ - public static Set getTargets(ItemStack stack, boolean checkBlockName) { - Set result = Sets.newHashSet(); - - stack.getOrDefault(ModDataComponents.MODULE_TARGET_LIST, ModuleTargetList.EMPTY).targets().forEach(target -> { - if (checkBlockName) { - target = updateTargetBlockName(stack, target); - } - if (target != null) { - result.add(target); - } - }); - - return result; - } - - private static void setTargetList(ItemStack stack, Set targets) { - stack.set(ModDataComponents.MODULE_TARGET_LIST, new ModuleTargetList(List.copyOf(targets))); - } - - private static ModuleTarget updateTargetBlockName(ItemStack stack, ModuleTarget target) { - ServerLevel level = MiscUtil.getWorldForGlobalPos(target.gPos); - BlockPos pos = target.gPos.pos(); - if (level != null && level.getChunkSource().hasChunk(pos.getX() >> 4, pos.getZ() >> 4)) { - String invName = BlockUtil.getBlockName(level, pos); - if (!target.blockTranslationKey.equals(invName)) { - setTarget(stack, level, pos, target.face); - return new ModuleTarget(target.gPos, target.face, invName); - } else { - return target; - } - } - return null; - } - - @Override - public void doModuleValidation(ItemStack stack, ServerPlayer player) { - ModuleTarget src = new ModuleTarget(MiscUtil.makeGlobalPos(player.getCommandSenderWorld(), player.blockPosition())); - Set targets = getMaxTargets() > 1 ? - getTargets(stack, true) : - Sets.newHashSet(getTarget(stack, true)); - for (ModuleTarget target : targets) { - if (target != null) { - TargetValidation v = validateTarget(stack, src, target, true); - MutableComponent msg = MiscUtil.asMutableComponent(target.getTextComponent()) - .append(" ").append(Component.translatable(v.translationKey()).withStyle(v.getColor())); - player.displayClientMessage(msg, false); - } - } - } - - /** - * Do some validation checks on the module's target. - * - * @param moduleStack the module's itemstack - * @param src position and dimension of the module (could be a router or player) - * @param dst position and dimension of the module's target - * @param validateBlocks true if the destination block should be validated; loaded and holding an inventory - * @return the validation result - */ - private TargetValidation validateTarget(ItemStack moduleStack, ModuleTarget src, ModuleTarget dst, boolean validateBlocks) { - if (isRangeLimited() && (!src.isSameWorld(dst) || src.gPos.pos().distSqr(dst.gPos.pos()) > maxDistanceSq(moduleStack))) { - return TargetValidation.OUT_OF_RANGE; - } - - // validateBlocks will be true only when this is called server-side by left-clicking the module in hand, - // or when the router is actually executing the module; - // we can't reliably validate chunk loading or inventory presence on the client (for tooltip generation) - if (validateBlocks) { - ServerLevel w = MiscUtil.getWorldForGlobalPos(dst.gPos); - if (w == null || !w.getChunkSource().hasChunk(dst.gPos.pos().getX() >> 4, dst.gPos.pos().getZ() >> 4)) { - return TargetValidation.NOT_LOADED; - } - if (w.getBlockEntity(dst.gPos.pos()) == null) { - return TargetValidation.NOT_INVENTORY; - } - if (badDimension(dst.gPos.dimension().location()) || badDimension(src.gPos.dimension().location())) { - return TargetValidation.BAD_DIMENSION; - } - } - return TargetValidation.OK; - } - - protected boolean badDimension(ResourceLocation dimId) { - return false; - } - - private int maxDistanceSq(ItemStack stack) { - if (stack.getItem() instanceof IRangedModule rangedModule) { - int r = rangedModule.getCurrentRange(stack); - return r * r; - } - return 0; - } - - protected int getMaxTargets() { - return 1; - } - - /** - * Does this module have limited range? - * - * @return true if range is limited, false otherwise - */ - protected boolean isRangeLimited() { - return true; - } - - enum TargetValidation implements StringRepresentable { - OK("ok"), - OUT_OF_RANGE("out_of_range"), - NOT_LOADED("not_loaded"), - NOT_INVENTORY("no_inventory"), - BAD_DIMENSION("bad_dimension"); - - private final String name; - - TargetValidation(String name) { - this.name = name; - } - - ChatFormatting getColor() { - return this == OK ? ChatFormatting.GREEN : ChatFormatting.RED; - } - - String translationKey() { - return "modularrouters.chatText.targetValidation." + getSerializedName(); - } - - @Override - public String getSerializedName() { - return name; - } - } -} diff --git a/src/main/java/me/desht/modularrouters/item/module/adapter/IItemAdapter.java b/src/main/java/me/desht/modularrouters/item/module/adapter/IItemAdapter.java new file mode 100644 index 00000000..7a9b2bf9 --- /dev/null +++ b/src/main/java/me/desht/modularrouters/item/module/adapter/IItemAdapter.java @@ -0,0 +1,35 @@ +package me.desht.modularrouters.item.module.adapter; + +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.InteractionResultHolder; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.context.UseOnContext; +import net.minecraft.world.level.Level; + +import java.util.List; + +public sealed interface IItemAdapter permits TargetedModuleAdapter, IItemAdapter.NoOp { + default InteractionResult useOn(UseOnContext ctx) { + return InteractionResult.PASS; + } + + default InteractionResultHolder onSneakRightClick(ItemStack stack, Level world, Player player, InteractionHand hand) { + return InteractionResultHolder.pass(stack); + } + + default void doModuleValidation(ItemStack stack, ServerPlayer player) { + + } + + default void addSettingsInformation(ItemStack stack, List list) { + + } + + default void addUsageInformation(ItemStack itemstack, List list) {} + + record NoOp() implements IItemAdapter {} +} diff --git a/src/main/java/me/desht/modularrouters/item/module/adapter/TargetedModuleAdapter.java b/src/main/java/me/desht/modularrouters/item/module/adapter/TargetedModuleAdapter.java new file mode 100644 index 00000000..2641272b --- /dev/null +++ b/src/main/java/me/desht/modularrouters/item/module/adapter/TargetedModuleAdapter.java @@ -0,0 +1,192 @@ +package me.desht.modularrouters.item.module.adapter; + +import me.desht.modularrouters.client.util.ClientUtil; +import me.desht.modularrouters.config.ConfigHolder; +import me.desht.modularrouters.core.ModSounds; +import me.desht.modularrouters.item.module.IRangedModule; +import me.desht.modularrouters.item.module.ITargetedModule; +import me.desht.modularrouters.item.module.TargetValidation; +import me.desht.modularrouters.logic.ModuleTarget; +import me.desht.modularrouters.util.BlockUtil; +import me.desht.modularrouters.util.MiscUtil; +import net.minecraft.ChatFormatting; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.GlobalPos; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.sounds.SoundSource; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.InteractionResultHolder; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.context.UseOnContext; +import net.minecraft.world.level.Level; + +import java.util.List; +import java.util.Set; + +import static me.desht.modularrouters.client.util.ClientUtil.xlate; +import static me.desht.modularrouters.util.MiscUtil.asMutableComponent; + +public record TargetedModuleAdapter(ITargetedModule targeted) implements IItemAdapter { + @Override + public InteractionResult useOn(UseOnContext ctx) { + if (ctx.getPlayer() != null && ctx.getPlayer().isShiftKeyDown()) { + if (targeted.getMaxTargets() == 1) { + if (ITargetedModule.canSelectTarget(ctx)) { + handleSingleTarget(ctx.getItemInHand(), ctx.getPlayer(), ctx.getLevel(), ctx.getClickedPos(), ctx.getClickedFace()); + return InteractionResult.SUCCESS; + } + } else { + return handleMultiTarget(ctx.getItemInHand(), ctx, ctx.getPlayer(), ctx.getLevel(), ctx.getClickedPos(), ctx.getClickedFace()); + } + } + return InteractionResult.PASS; + } + + @Override + public InteractionResultHolder onSneakRightClick(ItemStack stack, Level world, Player player, InteractionHand hand) { + if (!world.isClientSide && !ITargetedModule.getTargets(stack, false).isEmpty() && targeted.getMaxTargets() == 1) { + ITargetedModule.setTargets(stack, Set.of()); + world.playSound(null, player.blockPosition(), ModSounds.SUCCESS.get(), SoundSource.BLOCKS, + ConfigHolder.common.sound.bleepVolume.get().floatValue(), 1.1f); + player.displayClientMessage(Component.translatable("modularrouters.chatText.misc.targetCleared").withStyle(ChatFormatting.YELLOW), true); + } + return new InteractionResultHolder<>(InteractionResult.SUCCESS, stack); + } + + private void handleSingleTarget(ItemStack stack, Player player, Level world, BlockPos pos, Direction face) { + if (!world.isClientSide) { + ITargetedModule.setTargets(stack, Set.of(new ModuleTarget(world, pos, face))); + var tgts = ITargetedModule.getTargets(stack, true); + if (!tgts.isEmpty()) { + MutableComponent msg = Component.translatable("modularrouters.chatText.misc.targetSet").append(tgts.iterator().next().getTextComponent()); + player.displayClientMessage(msg.withStyle(ChatFormatting.YELLOW), true); + world.playSound(null, pos, ModSounds.SUCCESS.get(), SoundSource.BLOCKS, + ConfigHolder.common.sound.bleepVolume.get().floatValue(), 1.3f); + } + } + } + + private InteractionResult handleMultiTarget(ItemStack stack, UseOnContext context, Player player, Level world, BlockPos pos, Direction face) { + Set targets = ITargetedModule.getTargets(stack, !world.isClientSide); + String invName = BlockUtil.getBlockName(world, pos); + GlobalPos gPos = MiscUtil.makeGlobalPos(world, pos); + ModuleTarget tgt = new ModuleTarget(gPos, face, invName); + + // Allow removing targets without checking if they're valid + if (targets.contains(tgt)) { + if (world.isClientSide) return InteractionResult.SUCCESS; + targets.remove(tgt); + + player.displayClientMessage(Component.translatable("modularrouters.chatText.misc.targetRemoved", targets.size(), targeted.getMaxTargets()) + .append(tgt.getTextComponent()).withStyle(ChatFormatting.YELLOW), true); + world.playSound(null, pos, ModSounds.SUCCESS.get(), SoundSource.BLOCKS, ConfigHolder.common.sound.bleepVolume.get().floatValue(), 1.1f); + ITargetedModule.setTargets(stack, targets); + return InteractionResult.SUCCESS; + } + + if (ITargetedModule.canSelectTarget(context)) { + if (world.isClientSide) return InteractionResult.SUCCESS; + if (targets.size() < targeted.getMaxTargets()) { + targets.add(tgt); + player.displayClientMessage(Component.translatable("modularrouters.chatText.misc.targetAdded", targets.size(), targeted.getMaxTargets()) + .append(tgt.getTextComponent()).withStyle(ChatFormatting.YELLOW), true); + + world.playSound(null, pos, ModSounds.SUCCESS.get(), SoundSource.BLOCKS, + ConfigHolder.common.sound.bleepVolume.get().floatValue(), 1.3f); + ITargetedModule.setTargets(stack, targets); + } else { + // too many targets already + player.displayClientMessage(Component.translatable("modularrouters.chatText.misc.tooManyTargets", targeted.getMaxTargets()) + .withStyle(ChatFormatting.RED), true); + world.playSound(null, pos, ModSounds.ERROR.get(), SoundSource.BLOCKS, 1.0f, 1.3f); + } + + return InteractionResult.SUCCESS; + } + return InteractionResult.PASS; + } + + @Override + public void addSettingsInformation(ItemStack stack, List list) { + Set targets = ITargetedModule.getTargets(stack, false); + + for (ModuleTarget target : targets) { + if (target != null) { + Component msg = Component.literal("▶ ").append(asMutableComponent(target.getTextComponent()).withStyle(ChatFormatting.WHITE)); + list.add(msg); + ClientUtil.getOpenItemRouter().ifPresent(router -> { + ModuleTarget moduleTarget = new ModuleTarget(router.getGlobalPos()); + TargetValidation val = validateTarget(stack, moduleTarget, target, false); + if (val != TargetValidation.OK) { + list.add(xlate(val.translationKey()).withStyle(val.getColor())); + } + }); + } + } + } + + @Override + public void doModuleValidation(ItemStack stack, ServerPlayer player) { + ModuleTarget src = new ModuleTarget(MiscUtil.makeGlobalPos(player.getCommandSenderWorld(), player.blockPosition())); + Set targets = ITargetedModule.getTargets(stack, true); + for (ModuleTarget target : targets) { + if (target != null) { + TargetValidation v = validateTarget(stack, src, target, true); + MutableComponent msg = MiscUtil.asMutableComponent(target.getTextComponent()) + .append(" ").append(Component.translatable(v.translationKey()).withStyle(v.getColor())); + player.displayClientMessage(msg, false); + } + } + } + + /** + * Do some validation checks on the module's target. + * + * @param moduleStack the module's itemstack + * @param src position and dimension of the module (could be a router or player) + * @param dst position and dimension of the module's target + * @param validateBlocks true if the destination block should be validated; loaded and holding an inventory + * @return the validation result + */ + private TargetValidation validateTarget(ItemStack moduleStack, ModuleTarget src, ModuleTarget dst, boolean validateBlocks) { + if (targeted.isRangeLimited() && (!src.isSameWorld(dst) || src.gPos.pos().distSqr(dst.gPos.pos()) > maxDistanceSq(moduleStack))) { + return TargetValidation.OUT_OF_RANGE; + } + + // validateBlocks will be true only when this is called server-side by left-clicking the module in hand, + // or when the router is actually executing the module; + // we can't reliably validate chunk loading or inventory presence on the client (for tooltip generation) + if (validateBlocks) { + ServerLevel w = MiscUtil.getWorldForGlobalPos(dst.gPos); + if (w == null || !w.getChunkSource().hasChunk(dst.gPos.pos().getX() >> 4, dst.gPos.pos().getZ() >> 4)) { + return TargetValidation.NOT_LOADED; + } + if (w.getBlockEntity(dst.gPos.pos()) == null) { + return TargetValidation.NOT_INVENTORY; + } + if (!targeted.canOperateInDimension(dst.gPos.dimension()) || !targeted.canOperateInDimension(src.gPos.dimension())) { + return TargetValidation.BAD_DIMENSION; + } + } + return TargetValidation.OK; + } + + private int maxDistanceSq(ItemStack stack) { + if (stack.getItem() instanceof IRangedModule rangedModule) { + int r = rangedModule.getCurrentRange(stack); + return r * r; + } + return 0; + } + + @Override + public void addUsageInformation(ItemStack itemstack, List list) { + list.add(xlate(targeted.getMaxTargets() > 1 ? "modularrouters.itemText.targetingHintMulti" : "modularrouters.itemText.targetingHint").withStyle(ChatFormatting.YELLOW)); + } +} diff --git a/src/main/java/me/desht/modularrouters/logic/ModuleTarget.java b/src/main/java/me/desht/modularrouters/logic/ModuleTarget.java index 63d7e957..4add9637 100644 --- a/src/main/java/me/desht/modularrouters/logic/ModuleTarget.java +++ b/src/main/java/me/desht/modularrouters/logic/ModuleTarget.java @@ -2,8 +2,10 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; +import me.desht.modularrouters.util.BlockUtil; import me.desht.modularrouters.util.MiscUtil; import net.minecraft.ChatFormatting; +import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.GlobalPos; import net.minecraft.network.FriendlyByteBuf; @@ -56,6 +58,10 @@ public ModuleTarget(GlobalPos gPos, Direction face, String blockTranslationKey) this.blockTranslationKey = blockTranslationKey; } + public ModuleTarget(Level world, BlockPos pos, Direction face) { + this(MiscUtil.makeGlobalPos(world, pos), face, BlockUtil.getBlockName(world, pos)); + } + public ModuleTarget(GlobalPos gPos, Direction face) { this(gPos, face, ""); } diff --git a/src/main/java/me/desht/modularrouters/logic/ModuleTargetList.java b/src/main/java/me/desht/modularrouters/logic/ModuleTargetList.java index d4a5c937..5f801084 100644 --- a/src/main/java/me/desht/modularrouters/logic/ModuleTargetList.java +++ b/src/main/java/me/desht/modularrouters/logic/ModuleTargetList.java @@ -5,16 +5,13 @@ import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.codec.ByteBufCodecs; import net.minecraft.network.codec.StreamCodec; +import org.jetbrains.annotations.Unmodifiable; import java.util.List; -public record ModuleTargetList(List targets) { +public record ModuleTargetList(@Unmodifiable List targets) { public static final ModuleTargetList EMPTY = new ModuleTargetList(List.of()); - public static ModuleTargetList singleTarget(ModuleTarget target) { - return new ModuleTargetList(List.of(target)); - } - public static final Codec CODEC = RecordCodecBuilder.create(builder -> builder.group( ModuleTarget.CODEC.listOf().fieldOf("targets").forGetter(ModuleTargetList::targets) ).apply(builder, ModuleTargetList::new)); @@ -27,8 +24,4 @@ public static ModuleTargetList singleTarget(ModuleTarget target) { public boolean isEmpty() { return targets.isEmpty(); } - - public ModuleTarget getSingle() { - return targets.getFirst(); - } } diff --git a/src/main/java/me/desht/modularrouters/logic/compiled/CompiledDistributorModule.java b/src/main/java/me/desht/modularrouters/logic/compiled/CompiledDistributorModule.java index f2e6056b..0fc7ca4e 100644 --- a/src/main/java/me/desht/modularrouters/logic/compiled/CompiledDistributorModule.java +++ b/src/main/java/me/desht/modularrouters/logic/compiled/CompiledDistributorModule.java @@ -7,7 +7,7 @@ import me.desht.modularrouters.config.ConfigHolder; import me.desht.modularrouters.core.ModDataComponents; import me.desht.modularrouters.core.ModItems; -import me.desht.modularrouters.item.module.TargetedModule; +import me.desht.modularrouters.item.module.ITargetedModule; import me.desht.modularrouters.logic.ModuleTarget; import me.desht.modularrouters.logic.settings.TransferDirection; import me.desht.modularrouters.util.BeamData; @@ -85,7 +85,7 @@ protected int getBeamColor() { @Override protected List setupTargets(ModularRouterBlockEntity router, ItemStack stack) { - Set t = TargetedModule.getTargets(stack, router != null && !router.nonNullLevel().isClientSide); + Set t = ITargetedModule.getTargets(stack, router != null && !router.nonNullLevel().isClientSide); List l = Lists.newArrayList(t); if (router == null) return l; l.sort(Comparator.comparingDouble(o -> calcDist(o, router))); diff --git a/src/main/java/me/desht/modularrouters/logic/compiled/CompiledEnergyDistributorModule.java b/src/main/java/me/desht/modularrouters/logic/compiled/CompiledEnergyDistributorModule.java index cada8fd1..9598b295 100644 --- a/src/main/java/me/desht/modularrouters/logic/compiled/CompiledEnergyDistributorModule.java +++ b/src/main/java/me/desht/modularrouters/logic/compiled/CompiledEnergyDistributorModule.java @@ -1,9 +1,7 @@ package me.desht.modularrouters.logic.compiled; -import com.google.common.collect.ImmutableList; import me.desht.modularrouters.block.tile.ModularRouterBlockEntity; import me.desht.modularrouters.core.ModItems; -import me.desht.modularrouters.item.module.TargetedModule; import me.desht.modularrouters.logic.ModuleTarget; import me.desht.modularrouters.util.BeamData; import net.minecraft.world.item.ItemStack; @@ -20,9 +18,7 @@ public CompiledEnergyDistributorModule(@Nullable ModularRouterBlockEntity router @Override public boolean execute(@Nonnull ModularRouterBlockEntity router) { - List inRange = getTargets().stream() - .filter(target -> target.isSameWorld(router.getLevel()) && router.getBlockPos().distSqr(target.gPos.pos()) <= getRangeSquared()) - .toList(); + List inRange = getTargets(); if (inRange.isEmpty()) return false; int total = 0; @@ -46,11 +42,4 @@ public boolean execute(@Nonnull ModularRouterBlockEntity router) { return total > 0; } - - @Override - public List setupTargets(ModularRouterBlockEntity router, ItemStack stack) { - return router == null ? - List.of() : - ImmutableList.copyOf(TargetedModule.getTargets(stack, !router.getLevel().isClientSide)); - } } diff --git a/src/main/java/me/desht/modularrouters/logic/compiled/CompiledFluidModule1.java b/src/main/java/me/desht/modularrouters/logic/compiled/CompiledFluidModule.java similarity index 97% rename from src/main/java/me/desht/modularrouters/logic/compiled/CompiledFluidModule1.java rename to src/main/java/me/desht/modularrouters/logic/compiled/CompiledFluidModule.java index b134163a..fe49c7c3 100644 --- a/src/main/java/me/desht/modularrouters/logic/compiled/CompiledFluidModule1.java +++ b/src/main/java/me/desht/modularrouters/logic/compiled/CompiledFluidModule.java @@ -39,10 +39,10 @@ import java.util.Objects; import java.util.Optional; -public class CompiledFluidModule1 extends CompiledModule { +public class CompiledFluidModule extends CompiledModule { private final FluidModuleSettings settings; - public CompiledFluidModule1(ModularRouterBlockEntity router, ItemStack stack) { + public CompiledFluidModule(ModularRouterBlockEntity router, ItemStack stack) { super(router, stack); settings = stack.getOrDefault(ModDataComponents.FLUID_SETTINGS.get(), FluidModuleSettings.DEFAULT); @@ -254,12 +254,12 @@ public record FluidModuleSettings(int maxTransfer, TransferDirection direction, .forGetter(FluidModuleSettings::regulateAbsolute) ).apply(builder, FluidModuleSettings::new)); - public static StreamCodec STREAM_CODEC = StreamCodec.composite( + public static StreamCodec STREAM_CODEC = StreamCodec.composite( ByteBufCodecs.INT, FluidModuleSettings::maxTransfer, NeoForgeStreamCodecs.enumCodec(TransferDirection.class), FluidModuleSettings::direction, ByteBufCodecs.BOOL, FluidModuleSettings::forceEmpty, ByteBufCodecs.BOOL, FluidModuleSettings::regulateAbsolute, - CompiledFluidModule1.FluidModuleSettings::new + CompiledFluidModule.FluidModuleSettings::new ); } diff --git a/src/main/java/me/desht/modularrouters/logic/compiled/CompiledFluidModule2.java b/src/main/java/me/desht/modularrouters/logic/compiled/CompiledFluidModule2.java deleted file mode 100644 index c17a19b9..00000000 --- a/src/main/java/me/desht/modularrouters/logic/compiled/CompiledFluidModule2.java +++ /dev/null @@ -1,20 +0,0 @@ -package me.desht.modularrouters.logic.compiled; - -import me.desht.modularrouters.block.tile.ModularRouterBlockEntity; -import me.desht.modularrouters.item.module.TargetedModule; -import me.desht.modularrouters.logic.ModuleTarget; -import net.minecraft.world.item.ItemStack; - -import java.util.List; - -public class CompiledFluidModule2 extends CompiledFluidModule1 { - public CompiledFluidModule2(ModularRouterBlockEntity router, ItemStack stack) { - super(router, stack); - } - - @Override - protected List setupTargets(ModularRouterBlockEntity router, ItemStack stack) { - ModuleTarget target = TargetedModule.getTarget(stack, !router.nonNullLevel().isClientSide); - return target == null ? List.of() : List.of(target); - } -} 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 da19c5ca..73e4b751 100644 --- a/src/main/java/me/desht/modularrouters/logic/compiled/CompiledModule.java +++ b/src/main/java/me/desht/modularrouters/logic/compiled/CompiledModule.java @@ -6,6 +6,7 @@ import me.desht.modularrouters.core.ModItems; import me.desht.modularrouters.item.augment.AugmentItem.AugmentCounter; import me.desht.modularrouters.item.module.IRangedModule; +import me.desht.modularrouters.item.module.ITargetedModule; import me.desht.modularrouters.item.module.ModuleItem; import me.desht.modularrouters.logic.ModuleTarget; import me.desht.modularrouters.logic.filter.Filter; @@ -25,6 +26,7 @@ import org.apache.commons.lang3.Validate; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Unmodifiable; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -38,6 +40,7 @@ public abstract class CompiledModule { private final Filter filter; @Nullable private final Direction absoluteFacing; + @Unmodifiable private final List targets; @Nullable private final Direction routerFacing; @@ -65,7 +68,14 @@ protected CompiledModule(@Nullable ModularRouterBlockEntity router, ItemStack st commonSettings = ModuleItem.getCommonSettings(stack); range = module instanceof IRangedModule r ? r.getCurrentRange(getRangeModifier()) : 0; rangeSquared = range * range; - targets = setupTargets(router, stack); + + if (router == null) { + targets = setupTargets(null, stack); + } else { + targets = setupTargets(router, stack) + .stream().filter(t -> isTargetValid(router, t)) + .toList(); + } filter = new Filter(stack, shouldStoreRawFilterItems(), augmentCounter.getAugmentCount(ModItems.FILTER_ROUND_ROBIN_AUGMENT.get()) > 0); absoluteFacing = router == null ? null : router.getAbsoluteFacing(commonSettings.facing()); routerFacing = router == null ? null : router.getAbsoluteFacing(RelativeDirection.FRONT); @@ -117,6 +127,16 @@ public List getTargets() { public boolean hasTarget() { return targets != null && !targets.isEmpty(); } + protected boolean isTargetValid(ModularRouterBlockEntity router, ModuleTarget target) { + if (module instanceof ITargetedModule targetedModule && !targetedModule.isRangeLimited()) { + return (target.isSameWorld(router.getLevel()) || targetedModule.canOperateInDimension(target.gPos.dimension())) + && targetedModule.canOperateInDimension(router.getLevel().dimension()); + } else if (module instanceof IRangedModule) { + return target.isSameWorld(router.getLevel()) && router.getBlockPos().distSqr(target.gPos.pos()) <= getRangeSquared(); + } + return true; + } + public ModuleTermination termination() { return commonSettings.termination(); } @@ -209,9 +229,14 @@ private void setLastMatchPos(BlockPos key, int lastMatchPos) { * @param stack the module itemstack * @return a list of router target objects (for most modules this is a singleton list) */ + @Unmodifiable protected List setupTargets(ModularRouterBlockEntity router, ItemStack stack) { - if (router == null || (module.isDirectional() && getDirection() == RelativeDirection.NONE)) { + if (router == null) { + return null; + } else if (module.isDirectional() && getDirection() == RelativeDirection.NONE) { return null; + } else if (module instanceof ITargetedModule) { + return List.copyOf(ITargetedModule.getTargets(stack, !router.nonNullLevel().isClientSide)); } Direction facing = router.getAbsoluteFacing(getDirection()); BlockPos pos = router.getBlockPos().relative(facing); diff --git a/src/main/java/me/desht/modularrouters/logic/compiled/CompiledPullerModule1.java b/src/main/java/me/desht/modularrouters/logic/compiled/CompiledPullerModule1.java index 496a4741..ac1a4608 100644 --- a/src/main/java/me/desht/modularrouters/logic/compiled/CompiledPullerModule1.java +++ b/src/main/java/me/desht/modularrouters/logic/compiled/CompiledPullerModule1.java @@ -17,7 +17,7 @@ public CompiledPullerModule1(ModularRouterBlockEntity router, ItemStack stack) { public boolean execute(@Nonnull ModularRouterBlockEntity router) { if (!router.isBufferFull()) { ModuleTarget target = getTarget(); - if (target == null || !validateRange(router, getTarget())) { + if (target == null) { return false; } return target.getItemHandler().map(handler -> { diff --git a/src/main/java/me/desht/modularrouters/logic/compiled/CompiledPullerModule2.java b/src/main/java/me/desht/modularrouters/logic/compiled/CompiledPullerModule2.java index 6e9c472e..65165901 100644 --- a/src/main/java/me/desht/modularrouters/logic/compiled/CompiledPullerModule2.java +++ b/src/main/java/me/desht/modularrouters/logic/compiled/CompiledPullerModule2.java @@ -2,32 +2,15 @@ import me.desht.modularrouters.block.tile.ModularRouterBlockEntity; import me.desht.modularrouters.core.ModItems; -import me.desht.modularrouters.item.module.TargetedModule; -import me.desht.modularrouters.logic.ModuleTarget; import me.desht.modularrouters.util.BeamData; import net.minecraft.core.BlockPos; import net.minecraft.world.item.ItemStack; -import java.util.List; - public class CompiledPullerModule2 extends CompiledPullerModule1 { public CompiledPullerModule2(ModularRouterBlockEntity router, ItemStack stack) { super(router, stack); } - @Override - protected List setupTargets(ModularRouterBlockEntity router, ItemStack stack) { - ModuleTarget target = TargetedModule.getTarget(stack, !router.nonNullLevel().isClientSide); - return target == null ? List.of() : List.of(target); - } - - @Override - boolean validateRange(ModularRouterBlockEntity router, ModuleTarget target) { - return target != null - && target.isSameWorld(router.getLevel()) - && router.getBlockPos().distSqr(target.gPos.pos()) <= getRangeSquared(); - } - @Override protected void playParticles(ModularRouterBlockEntity router, BlockPos targetPos, ItemStack stack) { if (router.getUpgradeCount(ModItems.MUFFLER_UPGRADE.get()) < 2) { diff --git a/src/main/java/me/desht/modularrouters/logic/compiled/CompiledSenderModule2.java b/src/main/java/me/desht/modularrouters/logic/compiled/CompiledSenderModule2.java index 8203d4a9..23b52e4e 100644 --- a/src/main/java/me/desht/modularrouters/logic/compiled/CompiledSenderModule2.java +++ b/src/main/java/me/desht/modularrouters/logic/compiled/CompiledSenderModule2.java @@ -1,27 +1,18 @@ package me.desht.modularrouters.logic.compiled; import me.desht.modularrouters.block.tile.ModularRouterBlockEntity; -import me.desht.modularrouters.item.module.TargetedModule; import me.desht.modularrouters.logic.ModuleTarget; import net.minecraft.world.item.ItemStack; -import java.util.List; - public class CompiledSenderModule2 extends CompiledSenderModule1 { public CompiledSenderModule2(ModularRouterBlockEntity router, ItemStack stack) { super(router, stack); } - @Override - protected List setupTargets(ModularRouterBlockEntity router, ItemStack stack) { - ModuleTarget target = TargetedModule.getTarget(stack, !router.nonNullLevel().isClientSide); - return target == null ? List.of() : List.of(target); - } - @Override protected PositionedItemHandler findTargetInventory(ModularRouterBlockEntity router) { ModuleTarget target = getEffectiveTarget(router); - if (target == null || !validate(router, target)) { + if (target == null || !isTargetValid(router, target)) { return PositionedItemHandler.INVALID; } @@ -29,10 +20,6 @@ protected PositionedItemHandler findTargetInventory(ModularRouterBlockEntity rou .orElse(PositionedItemHandler.INVALID); } - protected boolean validate(ModularRouterBlockEntity router, ModuleTarget target) { - return target.isSameWorld(router.getLevel()) && router.getBlockPos().distSqr(target.gPos.pos()) <= getRangeSquared(); - } - @Override protected int getBeamColor() { return 0xFF8000; diff --git a/src/main/java/me/desht/modularrouters/logic/compiled/CompiledSenderModule3.java b/src/main/java/me/desht/modularrouters/logic/compiled/CompiledSenderModule3.java index 74fb3aa7..22affa81 100644 --- a/src/main/java/me/desht/modularrouters/logic/compiled/CompiledSenderModule3.java +++ b/src/main/java/me/desht/modularrouters/logic/compiled/CompiledSenderModule3.java @@ -1,9 +1,7 @@ package me.desht.modularrouters.logic.compiled; -import me.desht.modularrouters.ModularRouters; import me.desht.modularrouters.block.tile.ModularRouterBlockEntity; import me.desht.modularrouters.core.ModItems; -import me.desht.modularrouters.logic.ModuleTarget; import me.desht.modularrouters.logic.settings.RelativeDirection; import me.desht.modularrouters.util.BeamData; import net.minecraft.core.BlockPos; @@ -15,13 +13,6 @@ public CompiledSenderModule3(ModularRouterBlockEntity router, ItemStack stack) { super(router, stack); } - @Override - protected boolean validate(ModularRouterBlockEntity router, ModuleTarget target) { - return target.isSameWorld(router.getLevel()) || - !ModularRouters.getDimensionBlacklist().test(target.gPos.dimension().location()) - && !ModularRouters.getDimensionBlacklist().test(router.nonNullLevel().dimension().location()); - } - @Override protected void playParticles(ModularRouterBlockEntity router, BlockPos targetPos, ItemStack stack) { if (router.getUpgradeCount(ModItems.MUFFLER_UPGRADE.get()) < 2) { From 1d077c9bb16140b212565c16ecfcee54856031c8 Mon Sep 17 00:00:00 2001 From: Matyrobbrt Date: Thu, 21 Nov 2024 20:06:59 +0200 Subject: [PATCH 02/10] Remove some more needless overrides --- .../client/render/area/IPositionProvider.java | 5 ++++- .../modularrouters/item/module/DistributorModule.java | 8 -------- .../item/module/EnergyDistributorModule.java | 10 ---------- 3 files changed, 4 insertions(+), 19 deletions(-) diff --git a/src/main/java/me/desht/modularrouters/client/render/area/IPositionProvider.java b/src/main/java/me/desht/modularrouters/client/render/area/IPositionProvider.java index 18c78d51..9e58b21b 100644 --- a/src/main/java/me/desht/modularrouters/client/render/area/IPositionProvider.java +++ b/src/main/java/me/desht/modularrouters/client/render/area/IPositionProvider.java @@ -19,7 +19,10 @@ public interface IPositionProvider { * @return a list of block positions that has been retrieved from the itemstack */ default List getStoredPositions(@Nonnull ItemStack stack) { - return List.copyOf(ITargetedModule.getTargets(stack, false)); + if (stack.getItem() instanceof ITargetedModule) { + return List.copyOf(ITargetedModule.getTargets(stack, false)); + } + return List.of(); } /** diff --git a/src/main/java/me/desht/modularrouters/item/module/DistributorModule.java b/src/main/java/me/desht/modularrouters/item/module/DistributorModule.java index 7d10ffec..b040ab6f 100644 --- a/src/main/java/me/desht/modularrouters/item/module/DistributorModule.java +++ b/src/main/java/me/desht/modularrouters/item/module/DistributorModule.java @@ -1,6 +1,5 @@ package me.desht.modularrouters.item.module; -import com.google.common.collect.ImmutableList; import me.desht.modularrouters.client.util.ClientUtil; import me.desht.modularrouters.client.util.TintColor; import me.desht.modularrouters.config.ConfigHolder; @@ -8,7 +7,6 @@ import me.desht.modularrouters.core.ModDataComponents; import me.desht.modularrouters.core.ModItems; import me.desht.modularrouters.core.ModMenuTypes; -import me.desht.modularrouters.logic.ModuleTarget; import me.desht.modularrouters.logic.compiled.CompiledDistributorModule; import me.desht.modularrouters.logic.compiled.CompiledDistributorModule.DistributorSettings; import net.minecraft.ChatFormatting; @@ -16,7 +14,6 @@ import net.minecraft.world.inventory.MenuType; import net.minecraft.world.item.ItemStack; -import javax.annotation.Nonnull; import java.util.List; public class DistributorModule extends SenderModule2 { @@ -44,11 +41,6 @@ public MenuType getMenuType() { return ModMenuTypes.DISTRIBUTOR_MENU.get(); } - @Override - public List getStoredPositions(@Nonnull ItemStack stack) { - return ImmutableList.copyOf(ITargetedModule.getTargets(stack, false)); - } - @Override public TintColor getItemTint() { return TINT_COLOR; diff --git a/src/main/java/me/desht/modularrouters/item/module/EnergyDistributorModule.java b/src/main/java/me/desht/modularrouters/item/module/EnergyDistributorModule.java index e71aa8cc..ac238f89 100644 --- a/src/main/java/me/desht/modularrouters/item/module/EnergyDistributorModule.java +++ b/src/main/java/me/desht/modularrouters/item/module/EnergyDistributorModule.java @@ -1,19 +1,14 @@ package me.desht.modularrouters.item.module; -import com.google.common.collect.ImmutableList; import me.desht.modularrouters.client.render.area.IPositionProvider; import me.desht.modularrouters.client.util.TintColor; import me.desht.modularrouters.config.ConfigHolder; import me.desht.modularrouters.core.ModItems; -import me.desht.modularrouters.logic.ModuleTarget; import me.desht.modularrouters.logic.compiled.CompiledEnergyDistributorModule; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.context.UseOnContext; import net.neoforged.neoforge.capabilities.Capabilities; -import javax.annotation.Nonnull; -import java.util.List; - public class EnergyDistributorModule extends ModuleItem implements IRangedModule, IPositionProvider, ITargetedModule { private static final TintColor TINT_COLOR = new TintColor(79, 9, 90); @@ -42,11 +37,6 @@ public int getHardMaxRange() { return 48; } - @Override - public List getStoredPositions(@Nonnull ItemStack stack) { - return ImmutableList.copyOf(ITargetedModule.getTargets(stack, false)); - } - @Override public boolean isValidTarget(UseOnContext ctx) { return ctx.getLevel().getCapability(Capabilities.EnergyStorage.BLOCK, ctx.getClickedPos(), ctx.getClickedFace()) != null; From eaec883aa32589bc9546efdc96f60f869c7251f2 Mon Sep 17 00:00:00 2001 From: Matyrobbrt Date: Thu, 21 Nov 2024 22:16:38 +0200 Subject: [PATCH 03/10] Fix some more bugs --- .../modularrouters/item/module/FluidModule2.java | 4 ---- .../desht/modularrouters/item/module/ModuleItem.java | 2 +- .../modularrouters/item/module/PullerModule2.java | 5 ----- .../modularrouters/item/module/SenderModule2.java | 6 ------ .../modularrouters/item/module/SenderModule3.java | 5 ----- .../logic/compiled/CompiledModule.java | 12 ++++++------ 6 files changed, 7 insertions(+), 27 deletions(-) diff --git a/src/main/java/me/desht/modularrouters/item/module/FluidModule2.java b/src/main/java/me/desht/modularrouters/item/module/FluidModule2.java index cdd9d03b..5ae9d501 100644 --- a/src/main/java/me/desht/modularrouters/item/module/FluidModule2.java +++ b/src/main/java/me/desht/modularrouters/item/module/FluidModule2.java @@ -14,10 +14,6 @@ public boolean isValidTarget(UseOnContext ctx) { return !ctx.getLevel().isEmptyBlock(ctx.getClickedPos()); } - @Override - public boolean isDirectional() { - return false; - } @Override public int getBaseRange() { return ConfigHolder.common.module.fluid2BaseRange.get(); diff --git a/src/main/java/me/desht/modularrouters/item/module/ModuleItem.java b/src/main/java/me/desht/modularrouters/item/module/ModuleItem.java index 255a2d36..bff659c6 100644 --- a/src/main/java/me/desht/modularrouters/item/module/ModuleItem.java +++ b/src/main/java/me/desht/modularrouters/item/module/ModuleItem.java @@ -97,7 +97,7 @@ final public CompiledModule compile(ModularRouterBlockEntity router, ItemStack s public abstract TintColor getItemTint(); public boolean isDirectional() { - return true; + return !(this instanceof ITargetedModule); } public boolean isOmniDirectional() { return false; } diff --git a/src/main/java/me/desht/modularrouters/item/module/PullerModule2.java b/src/main/java/me/desht/modularrouters/item/module/PullerModule2.java index 3967f8e1..07620657 100644 --- a/src/main/java/me/desht/modularrouters/item/module/PullerModule2.java +++ b/src/main/java/me/desht/modularrouters/item/module/PullerModule2.java @@ -14,11 +14,6 @@ public PullerModule2() { super(ModItems.moduleProps(), CompiledPullerModule2::new); } - @Override - public boolean isDirectional() { - return false; - } - @Override public int getBaseRange() { return ConfigHolder.common.module.puller2BaseRange.get(); diff --git a/src/main/java/me/desht/modularrouters/item/module/SenderModule2.java b/src/main/java/me/desht/modularrouters/item/module/SenderModule2.java index 88b6d03e..fa55394e 100644 --- a/src/main/java/me/desht/modularrouters/item/module/SenderModule2.java +++ b/src/main/java/me/desht/modularrouters/item/module/SenderModule2.java @@ -22,12 +22,6 @@ public SenderModule2() { protected SenderModule2(Item.Properties properties, BiFunction compiler) { super(properties, compiler); } - - @Override - public boolean isDirectional() { - return false; - } - @Override public int getBaseRange() { return ConfigHolder.common.module.sender2BaseRange.get(); diff --git a/src/main/java/me/desht/modularrouters/item/module/SenderModule3.java b/src/main/java/me/desht/modularrouters/item/module/SenderModule3.java index 0c49d931..90bcdbeb 100644 --- a/src/main/java/me/desht/modularrouters/item/module/SenderModule3.java +++ b/src/main/java/me/desht/modularrouters/item/module/SenderModule3.java @@ -17,11 +17,6 @@ public SenderModule3() { super(ModItems.moduleProps(), CompiledSenderModule3::new); } - @Override - public boolean isDirectional() { - return false; - } - @Override public boolean isRangeLimited() { return false; 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 73e4b751..a3f4d45d 100644 --- a/src/main/java/me/desht/modularrouters/logic/compiled/CompiledModule.java +++ b/src/main/java/me/desht/modularrouters/logic/compiled/CompiledModule.java @@ -112,7 +112,7 @@ protected boolean shouldStoreRawFilterItems() { * @return the first target as set up by {@link #setupTargets(ModularRouterBlockEntity, ItemStack)} */ public ModuleTarget getTarget() { - return targets == null || targets.isEmpty() ? null : targets.getFirst(); + return targets.isEmpty() ? null : targets.getFirst(); } /** @@ -125,7 +125,9 @@ public List getTargets() { return targets; } - public boolean hasTarget() { return targets != null && !targets.isEmpty(); } + public boolean hasTarget() { + return !targets.isEmpty(); + } protected boolean isTargetValid(ModularRouterBlockEntity router, ModuleTarget target) { if (module instanceof ITargetedModule targetedModule && !targetedModule.isRangeLimited()) { @@ -231,10 +233,8 @@ private void setLastMatchPos(BlockPos key, int lastMatchPos) { */ @Unmodifiable protected List setupTargets(ModularRouterBlockEntity router, ItemStack stack) { - if (router == null) { - return null; - } else if (module.isDirectional() && getDirection() == RelativeDirection.NONE) { - return null; + if (router == null || (module.isDirectional() && getDirection() == RelativeDirection.NONE)) { + return List.of(); } else if (module instanceof ITargetedModule) { return List.copyOf(ITargetedModule.getTargets(stack, !router.nonNullLevel().isClientSide)); } From 9a9da56b1ace914a09438a07d791f32f935cb74a Mon Sep 17 00:00:00 2001 From: Matyrobbrt Date: Thu, 21 Nov 2024 23:44:24 +0200 Subject: [PATCH 04/10] Start work on gametest --- build.gradle | 11 +++ .../test/ModularRoutersTest.java | 30 +++++++ .../modularrouters/test/RouterTestHelper.java | 81 +++++++++++++++++++ .../test/module/SenderModuleTest.java | 52 ++++++++++++ .../resources/META-INF/neoforge.mods.toml | 13 +++ 5 files changed, 187 insertions(+) create mode 100644 src/test/java/me/desht/modularrouters/test/ModularRoutersTest.java create mode 100644 src/test/java/me/desht/modularrouters/test/RouterTestHelper.java create mode 100644 src/test/java/me/desht/modularrouters/test/module/SenderModuleTest.java create mode 100644 src/test/resources/META-INF/neoforge.mods.toml diff --git a/build.gradle b/build.gradle index d6a99aec..ce255650 100644 --- a/build.gradle +++ b/build.gradle @@ -53,6 +53,14 @@ repositories { } } +sourceSets { + test { + runs { + modIdentifier 'modularrouterstest' + } + } +} + group = project.mod_group_id base { @@ -81,10 +89,12 @@ runs { jvmArguments.addAll '-Xmx4G' + modSource project.sourceSets.test modSource project.sourceSets.main } client { + systemProperty 'neoforge.enableGameTest', 'true' // Comma-separated list of namespaces to load gametests from. Empty = all namespaces. systemProperty 'forge.enabledGameTestNamespaces', project.mod_id } @@ -112,6 +122,7 @@ sourceSets.main.resources { srcDir 'src/generated/resources' } dependencies { implementation "net.neoforged:neoforge:${neo_version}" + localRuntime(testImplementation("net.neoforged:testframework:${neo_version}")) implementation("mcjty.theoneprobe:theoneprobe:${top_version}") { transitive = false diff --git a/src/test/java/me/desht/modularrouters/test/ModularRoutersTest.java b/src/test/java/me/desht/modularrouters/test/ModularRoutersTest.java new file mode 100644 index 00000000..bb59d0e5 --- /dev/null +++ b/src/test/java/me/desht/modularrouters/test/ModularRoutersTest.java @@ -0,0 +1,30 @@ +package me.desht.modularrouters.test; + +import net.minecraft.resources.ResourceLocation; +import net.neoforged.bus.api.IEventBus; +import net.neoforged.fml.ModContainer; +import net.neoforged.fml.common.Mod; +import net.neoforged.testframework.conf.ClientConfiguration; +import net.neoforged.testframework.conf.Feature; +import net.neoforged.testframework.conf.FrameworkConfiguration; +import net.neoforged.testframework.conf.MissingDescriptionAction; +import net.neoforged.testframework.impl.MutableTestFramework; +import net.neoforged.testframework.summary.GitHubActionsStepSummaryDumper; +import org.lwjgl.glfw.GLFW; + +@Mod("modularrouterstest") +public class ModularRoutersTest { + public ModularRoutersTest(ModContainer container, IEventBus eventBus) { + final MutableTestFramework framework = FrameworkConfiguration.builder(ResourceLocation.fromNamespaceAndPath(container.getNamespace(), "tests")) + .clientConfiguration(() -> ClientConfiguration.builder() + .toggleOverlayKey(GLFW.GLFW_KEY_J) + .openManagerKey(GLFW.GLFW_KEY_N) + .build()) + .enable(Feature.CLIENT_SYNC, Feature.CLIENT_MODIFICATIONS) + .dumpers(new GitHubActionsStepSummaryDumper()) + .build() + .create(); + + framework.init(eventBus, container); + } +} diff --git a/src/test/java/me/desht/modularrouters/test/RouterTestHelper.java b/src/test/java/me/desht/modularrouters/test/RouterTestHelper.java new file mode 100644 index 00000000..2ea34c1b --- /dev/null +++ b/src/test/java/me/desht/modularrouters/test/RouterTestHelper.java @@ -0,0 +1,81 @@ +package me.desht.modularrouters.test; + +import me.desht.modularrouters.block.tile.ModularRouterBlockEntity; +import me.desht.modularrouters.core.ModBlocks; +import me.desht.modularrouters.core.ModDataComponents; +import me.desht.modularrouters.logic.ModuleTarget; +import me.desht.modularrouters.logic.ModuleTargetList; +import me.desht.modularrouters.logic.settings.ModuleFlags; +import me.desht.modularrouters.logic.settings.ModuleSettings; +import me.desht.modularrouters.logic.settings.ModuleTermination; +import me.desht.modularrouters.logic.settings.RedstoneBehaviour; +import me.desht.modularrouters.logic.settings.RelativeDirection; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.gametest.framework.GameTestInfo; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.entity.ChestBlockEntity; +import net.neoforged.neoforge.items.IItemHandlerModifiable; +import net.neoforged.testframework.gametest.ExtendedGameTestHelper; + +import java.util.List; +import java.util.function.Supplier; + +public class RouterTestHelper extends ExtendedGameTestHelper { + public RouterTestHelper(GameTestInfo info) { + super(info); + } + + public ItemStack addDirectionalModule(ModularRouterBlockEntity router, Supplier module, RelativeDirection direction) { + var stack = module.get().getDefaultInstance(); + stack.set(ModDataComponents.COMMON_MODULE_SETTINGS, new ModuleSettings( + ModuleFlags.DEFAULT, + direction, + ModuleTermination.NONE, + RedstoneBehaviour.ALWAYS, + 0 + )); + return addModule(router, stack); + } + + public ItemStack addTargetedModule(ModularRouterBlockEntity router, Supplier module, int x, int y, int z, Direction face) { + var stack = module.get().getDefaultInstance(); + stack.set(ModDataComponents.MODULE_TARGET_LIST, new ModuleTargetList( + List.of(new ModuleTarget(getLevel(), absolutePos(new BlockPos(x, y, z)), face)) + )); + return addModule(router, stack); + } + + public ItemStack addModule(ModularRouterBlockEntity router, ItemStack module) { + router.getModules().insertItem(0, module, false); + return module; + } + + public ModularRouterBlockEntity placeRouter(int x, int y, int z) { + setBlock(x, y, z, ModBlocks.MODULAR_ROUTER.get()); + var router = getBlockEntity(x, y, z, ModularRouterBlockEntity.class); + addEndListener(success -> { + if (success) { + router.setBufferItemStack(ItemStack.EMPTY); + + for (int i = 0; i < router.getModuleSlotCount(); i++) { + router.getModules().setStackInSlot(i, ItemStack.EMPTY); + } + + for (int i = 0; i < router.getUpgradeSlotCount(); i++) { + ((IItemHandlerModifiable)router.getUpgrades()).setStackInSlot(i, ItemStack.EMPTY); + } + + setBlock(x, y, z, Blocks.AIR); + } + }); + return router; + } + + public ChestBlockEntity placeChest(int x, int y, int z) { + setBlock(x, y, z, Blocks.CHEST); + return getBlockEntity(x, y, z, ChestBlockEntity.class); + } +} diff --git a/src/test/java/me/desht/modularrouters/test/module/SenderModuleTest.java b/src/test/java/me/desht/modularrouters/test/module/SenderModuleTest.java new file mode 100644 index 00000000..9806a4ef --- /dev/null +++ b/src/test/java/me/desht/modularrouters/test/module/SenderModuleTest.java @@ -0,0 +1,52 @@ +package me.desht.modularrouters.test.module; + +import me.desht.modularrouters.core.ModItems; +import me.desht.modularrouters.logic.settings.RelativeDirection; +import me.desht.modularrouters.test.RouterTestHelper; +import net.minecraft.core.Direction; +import net.minecraft.gametest.framework.GameTest; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.neoforged.testframework.annotation.TestHolder; +import net.neoforged.testframework.gametest.EmptyTemplate; + +public class SenderModuleTest { + @GameTest + @TestHolder + @EmptyTemplate + static void testSenderMk1(final RouterTestHelper helper) { + var router = helper.placeRouter(1, 1, 1); + var chest = helper.placeChest(1, 2, 1); + + router.insertBuffer(new ItemStack(Items.APPLE, 64)); + helper.addDirectionalModule(router, ModItems.SENDER_MODULE_1, RelativeDirection.UP); + + helper.startSequence() + .thenIdle(20) + .thenExecute(() -> helper.assertValueEqual(chest.countItem(Items.APPLE), 1, "chest contents")) + .thenIdle(40) + .thenExecute(() -> helper.assertValueEqual(chest.countItem(Items.APPLE), 3, "chest contents")) + .thenExecute(() -> helper.assertValueEqual(router.getBufferItemStack().getCount(), 61, "router contents")) + .thenSucceed(); + } + + @GameTest + @TestHolder + @EmptyTemplate("5x5x5") + static void testSenderMk2(final RouterTestHelper helper) { + var router = helper.placeRouter(3, 4, 3); + var chest = helper.placeChest(0, 1, 0); + + router.insertBuffer(new ItemStack(Items.APPLE, 64)); + helper.addTargetedModule(router, ModItems.SENDER_MODULE_2, 0, 1, 0, Direction.UP); + + helper.startSequence() + .thenIdle(20) + .thenExecute(() -> helper.assertValueEqual(chest.countItem(Items.APPLE), 1, "chest contents")) + .thenIdle(40) + .thenExecute(() -> helper.assertValueEqual(chest.countItem(Items.APPLE), 3, "chest contents")) + .thenExecute(() -> helper.assertValueEqual(router.getBufferItemStack().getCount(), 61, "router contents")) + + .thenSucceed(); + } +} diff --git a/src/test/resources/META-INF/neoforge.mods.toml b/src/test/resources/META-INF/neoforge.mods.toml new file mode 100644 index 00000000..fd37c0ed --- /dev/null +++ b/src/test/resources/META-INF/neoforge.mods.toml @@ -0,0 +1,13 @@ +modLoader="javafml" +loaderVersion="[2,)" +issueTrackerURL="https://github.com/desht/ModularRouters/issues" +displayURL="https://minecraft.curseforge.com/projects/modular-routers" +credits="" +authors="" +license="MIT" + +[[mods]] #mandatory +modId="modularrouterstest" #mandatory +version="1.0" #mandatory +displayName="Modular Routers Test" +description="Modular Routers Test" From a6353cf32ee627c3542d67c906535c823116fd6c Mon Sep 17 00:00:00 2001 From: Matyrobbrt Date: Fri, 22 Nov 2024 19:15:08 +0200 Subject: [PATCH 05/10] More work on tests --- .github/workflows/main.yml | 4 +- .github/workflows/prs.yml | 28 ++++++ .../block/tile/ModularRouterBlockEntity.java | 2 +- .../compiled/CompiledDetectorModule.java | 2 +- .../compiled/CompiledExtruderModule1.java | 2 +- .../logic/compiled/CompiledModule.java | 4 +- .../logic/compiled/CompiledPlayerModule.java | 2 +- .../desht/modularrouters/test/RouterTest.java | 66 +++++++++++++ .../modularrouters/test/RouterTestHelper.java | 96 +++++++++++++------ .../test/module/SenderModuleTest.java | 63 ++++++++---- 10 files changed, 218 insertions(+), 51 deletions(-) create mode 100644 .github/workflows/prs.yml create mode 100644 src/test/java/me/desht/modularrouters/test/RouterTest.java diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 03948a5a..1f4b0087 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,4 +1,4 @@ -name: "Build applicable branches on push or pull request" +name: "Build applicable branches on push" # Controls when the action will run. Triggers the workflow on push or pull request # events for the branches listed @@ -53,4 +53,4 @@ jobs: if: env.DISCORD_WEBHOOK != null with: - args: 'Build complete for project {{ EVENT_PAYLOAD.repository.full_name }} (Minecraft ${{ steps.mod_meta.outputs.mc_version}}): ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}.' \ No newline at end of file + args: 'Build complete for project {{ EVENT_PAYLOAD.repository.full_name }} (Minecraft ${{ steps.mod_meta.outputs.mc_version}}): ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}.' diff --git a/.github/workflows/prs.yml b/.github/workflows/prs.yml new file mode 100644 index 00000000..a4500a15 --- /dev/null +++ b/.github/workflows/prs.yml @@ -0,0 +1,28 @@ +name: Build PRs + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + fetch-tags: true + + - name: Setup JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Build with Gradle + run: ./gradlew build + + - name: Run gametests + run: ./gradlew gameTestServer 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 dd0fff6e..ee394ca4 100644 --- a/src/main/java/me/desht/modularrouters/block/tile/ModularRouterBlockEntity.java +++ b/src/main/java/me/desht/modularrouters/block/tile/ModularRouterBlockEntity.java @@ -446,7 +446,7 @@ 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.shouldExecute() && cm.getEnergyCost() <= getEnergyStorage().getEnergyStored() && cm.shouldExecute(powered, pulsed)) { var event = cm.getEvent(); if (event != null) { event.setExecuted(false); diff --git a/src/main/java/me/desht/modularrouters/logic/compiled/CompiledDetectorModule.java b/src/main/java/me/desht/modularrouters/logic/compiled/CompiledDetectorModule.java index 160d41ef..180d6343 100644 --- a/src/main/java/me/desht/modularrouters/logic/compiled/CompiledDetectorModule.java +++ b/src/main/java/me/desht/modularrouters/logic/compiled/CompiledDetectorModule.java @@ -23,7 +23,7 @@ public CompiledDetectorModule(ModularRouterBlockEntity router, ItemStack stack) } @Override - public boolean hasTarget() { + public boolean shouldExecute() { return true; } diff --git a/src/main/java/me/desht/modularrouters/logic/compiled/CompiledExtruderModule1.java b/src/main/java/me/desht/modularrouters/logic/compiled/CompiledExtruderModule1.java index ffefd198..166ed0d0 100644 --- a/src/main/java/me/desht/modularrouters/logic/compiled/CompiledExtruderModule1.java +++ b/src/main/java/me/desht/modularrouters/logic/compiled/CompiledExtruderModule1.java @@ -104,7 +104,7 @@ void tryPushEntities(Level world, BlockPos placePos, Direction facing) { } @Override - public boolean shouldRun(boolean powered, boolean pulsed) { + public boolean shouldExecute(boolean powered, boolean pulsed) { 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 a3f4d45d..6f25a1d9 100644 --- a/src/main/java/me/desht/modularrouters/logic/compiled/CompiledModule.java +++ b/src/main/java/me/desht/modularrouters/logic/compiled/CompiledModule.java @@ -125,7 +125,7 @@ public List getTargets() { return targets; } - public boolean hasTarget() { + public boolean shouldExecute() { return !targets.isEmpty(); } @@ -325,7 +325,7 @@ public ModuleTarget getEffectiveTarget(ModularRouterBlockEntity router) { } @ApiStatus.OverrideOnly - public boolean shouldRun(boolean powered, boolean pulsed) { + public boolean shouldExecute(boolean powered, boolean pulsed) { return getRedstoneBehaviour().shouldRun(powered, pulsed); } diff --git a/src/main/java/me/desht/modularrouters/logic/compiled/CompiledPlayerModule.java b/src/main/java/me/desht/modularrouters/logic/compiled/CompiledPlayerModule.java index 9ce7c16c..ec30ebf6 100644 --- a/src/main/java/me/desht/modularrouters/logic/compiled/CompiledPlayerModule.java +++ b/src/main/java/me/desht/modularrouters/logic/compiled/CompiledPlayerModule.java @@ -50,7 +50,7 @@ public CompiledPlayerModule(ModularRouterBlockEntity router, ItemStack stack) { } @Override - public boolean hasTarget() { + public boolean shouldExecute() { return getPlayer() != null; } diff --git a/src/test/java/me/desht/modularrouters/test/RouterTest.java b/src/test/java/me/desht/modularrouters/test/RouterTest.java new file mode 100644 index 00000000..0ba8ed30 --- /dev/null +++ b/src/test/java/me/desht/modularrouters/test/RouterTest.java @@ -0,0 +1,66 @@ +package me.desht.modularrouters.test; + +import com.mojang.serialization.Codec; +import me.desht.modularrouters.block.tile.ModularRouterBlockEntity; +import me.desht.modularrouters.client.util.TintColor; +import me.desht.modularrouters.core.ModItems; +import me.desht.modularrouters.item.module.ModuleItem; +import me.desht.modularrouters.logic.compiled.CompiledModule; +import net.minecraft.gametest.framework.GameTest; +import net.minecraft.world.item.ItemStack; +import net.neoforged.neoforge.attachment.AttachmentType; +import net.neoforged.neoforge.registries.NeoForgeRegistries; +import net.neoforged.testframework.DynamicTest; +import net.neoforged.testframework.annotation.TestHolder; +import net.neoforged.testframework.gametest.EmptyTemplate; +import net.neoforged.testframework.registration.RegistrationHelper; +import org.jetbrains.annotations.NotNull; + +public class RouterTest { + @GameTest + @TestHolder + @EmptyTemplate + static void testRouterSpeed(DynamicTest test, RegistrationHelper reg) { + var counter = reg.registrar(NeoForgeRegistries.Keys.ATTACHMENT_TYPES) + .register("counter", () -> AttachmentType.builder(() -> 0) + .serialize(Codec.INT).build()); + + var module = reg.items().register("test_module", () -> new ModuleItem(ModItems.moduleProps(), (r, s) -> new CompiledModule(r, s) { + @Override + public boolean execute(@NotNull ModularRouterBlockEntity router) { + router.setData(counter, router.getData(counter.get()) + 1); + return true; + } + + @Override + public boolean shouldExecute() { + return true; + } + }) { + @Override + public TintColor getItemTint() { + return TintColor.BLACK; + } + + @Override + public int getEnergyCost(ItemStack stack) { + return 0; + } + }); + + test.onGameTest(RouterTestHelper.class, helper -> { + var router = helper.placeRouter(1, 2, 1); + router.addModule(new ItemStack(module.asItem())); + + helper.startSequence() + .thenIdle(40) // 2 router ticks + .thenExecute(() -> helper.assertValueEqual(router.router().getData(counter), 2, "counter")) + + // add 5 speed upgrades which will increase tick rate by 10 + .thenExecute(() -> router.addUpgrade(ModItems.SPEED_UPGRADE.toStack(5))) + .thenIdle(30) // 3 router ticks + .thenExecute(() -> helper.assertValueEqual(router.router().getData(counter), 5, "counter")) + .thenSucceed(); + }); + } +} diff --git a/src/test/java/me/desht/modularrouters/test/RouterTestHelper.java b/src/test/java/me/desht/modularrouters/test/RouterTestHelper.java index 2ea34c1b..e82b0bb5 100644 --- a/src/test/java/me/desht/modularrouters/test/RouterTestHelper.java +++ b/src/test/java/me/desht/modularrouters/test/RouterTestHelper.java @@ -1,8 +1,11 @@ package me.desht.modularrouters.test; import me.desht.modularrouters.block.tile.ModularRouterBlockEntity; +import me.desht.modularrouters.config.ConfigHolder; +import me.desht.modularrouters.container.handler.AugmentHandler; import me.desht.modularrouters.core.ModBlocks; import me.desht.modularrouters.core.ModDataComponents; +import me.desht.modularrouters.core.ModItems; import me.desht.modularrouters.logic.ModuleTarget; import me.desht.modularrouters.logic.ModuleTargetList; import me.desht.modularrouters.logic.settings.ModuleFlags; @@ -21,39 +24,16 @@ import net.neoforged.testframework.gametest.ExtendedGameTestHelper; import java.util.List; +import java.util.function.Consumer; import java.util.function.Supplier; +import java.util.stream.IntStream; public class RouterTestHelper extends ExtendedGameTestHelper { public RouterTestHelper(GameTestInfo info) { super(info); } - public ItemStack addDirectionalModule(ModularRouterBlockEntity router, Supplier module, RelativeDirection direction) { - var stack = module.get().getDefaultInstance(); - stack.set(ModDataComponents.COMMON_MODULE_SETTINGS, new ModuleSettings( - ModuleFlags.DEFAULT, - direction, - ModuleTermination.NONE, - RedstoneBehaviour.ALWAYS, - 0 - )); - return addModule(router, stack); - } - - public ItemStack addTargetedModule(ModularRouterBlockEntity router, Supplier module, int x, int y, int z, Direction face) { - var stack = module.get().getDefaultInstance(); - stack.set(ModDataComponents.MODULE_TARGET_LIST, new ModuleTargetList( - List.of(new ModuleTarget(getLevel(), absolutePos(new BlockPos(x, y, z)), face)) - )); - return addModule(router, stack); - } - - public ItemStack addModule(ModularRouterBlockEntity router, ItemStack module) { - router.getModules().insertItem(0, module, false); - return module; - } - - public ModularRouterBlockEntity placeRouter(int x, int y, int z) { + public RouterWrapper placeRouter(int x, int y, int z) { setBlock(x, y, z, ModBlocks.MODULAR_ROUTER.get()); var router = getBlockEntity(x, y, z, ModularRouterBlockEntity.class); addEndListener(success -> { @@ -71,11 +51,73 @@ public ModularRouterBlockEntity placeRouter(int x, int y, int z) { setBlock(x, y, z, Blocks.AIR); } }); - return router; + return new RouterWrapper(router, this); } public ChestBlockEntity placeChest(int x, int y, int z) { setBlock(x, y, z, Blocks.CHEST); return getBlockEntity(x, y, z, ChestBlockEntity.class); } + + public record RouterWrapper(ModularRouterBlockEntity router, RouterTestHelper helper) { + public ItemStack addDirectionalModule(Supplier module, RelativeDirection direction) { + var stack = module.get().getDefaultInstance(); + stack.set(ModDataComponents.COMMON_MODULE_SETTINGS, new ModuleSettings( + ModuleFlags.DEFAULT, + direction, + ModuleTermination.NONE, + RedstoneBehaviour.ALWAYS, + 0 + )); + return addModule(stack); + } + + public ItemStack addTargetedModule(Supplier module, int x, int y, int z, Direction face) { + var stack = module.get().getDefaultInstance(); + stack.set(ModDataComponents.MODULE_TARGET_LIST, new ModuleTargetList( + List.of(new ModuleTarget(helper.getLevel(), helper.absolutePos(new BlockPos(x, y, z)), face)) + )); + return addModule(stack); + } + + public ItemStack addModule(ItemStack module) { + router.getModules().insertItem(0, module, false); + return module; + } + + public ItemStack addUpgrade(ItemStack module) { + router.getUpgrades().insertItem(0, module, false); + return module; + } + + public ItemStack insertBuffer(ItemStack stack) { + router.insertBuffer(stack); + return stack; + } + + public void modifyModule(int index, Consumer mod) { + var module = router.getModules().getStackInSlot(index).copy(); + mod.accept(module); + router.getModules().setStackInSlot(index, module); + } + + public void modifyAugments(int moduleIndex, Consumer handler) { + modifyModule(moduleIndex, stack -> handler.accept(new AugmentHandler(stack, router))); + } + + public void clearBuffer() { + router.extractBuffer(router.getBufferItemStack().getCount()); + } + + public ItemStack getBuffer() { + return router.getBufferItemStack(); + } + + public int routerTicks(int routerTicks) { + return (20 - IntStream.range(0, router().getUpgradeSlotCount()) + .mapToObj(router().getUpgrades()::getStackInSlot) + .filter(s -> !s.isEmpty() && s.is(ModItems.SPEED_UPGRADE)) + .mapToInt(ItemStack::getCount).sum() * 2) * routerTicks; + } + } } diff --git a/src/test/java/me/desht/modularrouters/test/module/SenderModuleTest.java b/src/test/java/me/desht/modularrouters/test/module/SenderModuleTest.java index 9806a4ef..ef213e17 100644 --- a/src/test/java/me/desht/modularrouters/test/module/SenderModuleTest.java +++ b/src/test/java/me/desht/modularrouters/test/module/SenderModuleTest.java @@ -7,46 +7,77 @@ import net.minecraft.gametest.framework.GameTest; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; +import net.minecraft.world.level.block.entity.ChestBlockEntity; import net.neoforged.testframework.annotation.TestHolder; import net.neoforged.testframework.gametest.EmptyTemplate; +import net.neoforged.testframework.gametest.ExtendedSequence; public class SenderModuleTest { - @GameTest @TestHolder @EmptyTemplate + @GameTest(timeoutTicks = 1000) static void testSenderMk1(final RouterTestHelper helper) { var router = helper.placeRouter(1, 1, 1); var chest = helper.placeChest(1, 2, 1); - router.insertBuffer(new ItemStack(Items.APPLE, 64)); - helper.addDirectionalModule(router, ModItems.SENDER_MODULE_1, RelativeDirection.UP); + router.addDirectionalModule(ModItems.SENDER_MODULE_1, RelativeDirection.UP); - helper.startSequence() - .thenIdle(20) - .thenExecute(() -> helper.assertValueEqual(chest.countItem(Items.APPLE), 1, "chest contents")) - .thenIdle(40) - .thenExecute(() -> helper.assertValueEqual(chest.countItem(Items.APPLE), 3, "chest contents")) - .thenExecute(() -> helper.assertValueEqual(router.getBufferItemStack().getCount(), 61, "router contents")) - .thenSucceed(); + testTransfer(helper.startSequence(), helper, router, chest).thenSucceed(); } - @GameTest @TestHolder @EmptyTemplate("5x5x5") + @GameTest(timeoutTicks = 1000) static void testSenderMk2(final RouterTestHelper helper) { var router = helper.placeRouter(3, 4, 3); var chest = helper.placeChest(0, 1, 0); + router.addTargetedModule(ModItems.SENDER_MODULE_2, 0, 1, 0, Direction.UP); + + testTransfer(helper.startSequence(), helper, router, chest).thenSucceed(); + } + + @GameTest + @TestHolder + @EmptyTemplate("5x5x5") + static void testSenderMk2Range(final RouterTestHelper helper) { + var router = helper.placeRouter(3, 4, 3); + helper.placeChest(0, 1, 0); + + router.addTargetedModule(ModItems.SENDER_MODULE_2, 0, 1, 0, Direction.UP); + // reduce the range to 2 blocks which is not enough + router.modifyAugments(0, h -> h.insertItem(0, ModItems.RANGE_DOWN_AUGMENT.toStack(22), false)); router.insertBuffer(new ItemStack(Items.APPLE, 64)); - helper.addTargetedModule(router, ModItems.SENDER_MODULE_2, 0, 1, 0, Direction.UP); + // make sure we haven't sent anything helper.startSequence() - .thenIdle(20) + .thenIdle(router.routerTicks(2)) + .thenExecute(() -> helper.assertContainerEmpty(0, 1, 0)) + .thenExecute(() -> helper.assertValueEqual(router.getBuffer().getCount(), 64, "chest contents")) + .thenSucceed(); + } + + private static ExtendedSequence testTransfer(ExtendedSequence seq, RouterTestHelper helper, RouterTestHelper.RouterWrapper router, ChestBlockEntity chest) { + return seq + .thenExecute(() -> router.insertBuffer(new ItemStack(Items.APPLE, 64))) + + .thenIdle(router.routerTicks(1)) .thenExecute(() -> helper.assertValueEqual(chest.countItem(Items.APPLE), 1, "chest contents")) - .thenIdle(40) + .thenIdle(router.routerTicks(2)) .thenExecute(() -> helper.assertValueEqual(chest.countItem(Items.APPLE), 3, "chest contents")) - .thenExecute(() -> helper.assertValueEqual(router.getBufferItemStack().getCount(), 61, "router contents")) - .thenSucceed(); + // 2 stack upgrades - 4 items / tick + .thenExecute(() -> router.addUpgrade(ModItems.STACK_UPGRADE.toStack(2))) + .thenIdle(router.routerTicks(2)) + .thenExecute(() -> helper.assertValueEqual(chest.countItem(Items.APPLE), 11, "chest contents")) + + // 4 stack augments - 16 items / tick (stack upgrades are ignored) + .thenExecute(() -> router.modifyAugments(0, aug -> aug.insertItem(0, ModItems.STACK_AUGMENT.toStack(4), false))) + .thenIdle(router.routerTicks(1)) + .thenExecute(() -> helper.assertValueEqual(chest.countItem(Items.APPLE), 27, "chest contents")) + + .thenExecute(() -> helper.assertValueEqual(router.getBuffer().getCount(), 37, "router contents")) + + .thenExecute(router::clearBuffer); } } From 51465f8bb3b9ceaedf8ffe53a6a45f4721a8e5a0 Mon Sep 17 00:00:00 2001 From: Matyrobbrt Date: Fri, 22 Nov 2024 19:18:16 +0200 Subject: [PATCH 06/10] Fix (lack of) negation --- .../java/me/desht/modularrouters/item/module/SenderModule3.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/me/desht/modularrouters/item/module/SenderModule3.java b/src/main/java/me/desht/modularrouters/item/module/SenderModule3.java index 90bcdbeb..40ea61dd 100644 --- a/src/main/java/me/desht/modularrouters/item/module/SenderModule3.java +++ b/src/main/java/me/desht/modularrouters/item/module/SenderModule3.java @@ -39,6 +39,6 @@ public int getEnergyCost(ItemStack stack) { @Override public boolean canOperateInDimension(ResourceKey dimension) { - return ModularRouters.getDimensionBlacklist().test(dimension.location()); + return !ModularRouters.getDimensionBlacklist().test(dimension.location()); } } From 91ba9ef24cf6f7a78be8e2f75f36c5a89ea48c3e Mon Sep 17 00:00:00 2001 From: Matyrobbrt Date: Fri, 22 Nov 2024 19:45:33 +0200 Subject: [PATCH 07/10] No JUnit --- build.gradle | 4 ++++ gradle.properties | 3 +++ 2 files changed, 7 insertions(+) diff --git a/build.gradle b/build.gradle index ce255650..2dfa1c96 100644 --- a/build.gradle +++ b/build.gradle @@ -199,6 +199,10 @@ tasks.withType(JavaCompile).configureEach { options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation } +test { + enabled = false +} + Closure getReleaseType = { type -> switch(type) { case "alpha": return ReleaseType.ALPHA diff --git a/gradle.properties b/gradle.properties index e153c769..0f88abda 100644 --- a/gradle.properties +++ b/gradle.properties @@ -31,3 +31,6 @@ jade_curse_id=5639932 jei_version=19.16.4.161 patchouli_version=1.21-87-NEOFORGE ffs_version=21.0.0 + +# Prevent NG attempting to make the test sourceset a junit one +neogradle.subsystems.conventions.sourcesets.automatic-inclusion=false From 4635f78011c99897c75bccb5309904cf951096b2 Mon Sep 17 00:00:00 2001 From: Matyrobbrt Date: Fri, 22 Nov 2024 20:11:50 +0200 Subject: [PATCH 08/10] Fix task name --- .github/workflows/prs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/prs.yml b/.github/workflows/prs.yml index a4500a15..f770a2f2 100644 --- a/.github/workflows/prs.yml +++ b/.github/workflows/prs.yml @@ -25,4 +25,4 @@ jobs: run: ./gradlew build - name: Run gametests - run: ./gradlew gameTestServer + run: ./gradlew runGameTestServer From 8f08477dc93f546cd3a59aa62afedad3b5f1c590 Mon Sep 17 00:00:00 2001 From: Matyrobbrt Date: Sat, 23 Nov 2024 20:41:20 +0200 Subject: [PATCH 09/10] Test player and vacuum modules Also fix some bugs --- build.gradle | 10 ++ .../block/tile/ModularRouterBlockEntity.java | 2 +- .../compiled/CompiledExtruderModule1.java | 2 +- .../logic/compiled/CompiledModule.java | 2 +- .../logic/compiled/CompiledPlayerModule.java | 13 +-- .../logic/compiled/CompiledVacuumModule.java | 46 ++++++---- .../resources/META-INF/accesstransformer.cfg | 1 + .../test/ModularRoutersTest.java | 4 + .../modularrouters/test/RouterTestHelper.java | 73 ++++++++++++++- .../test/module/PlayerModuleTest.java | 47 ++++++++++ .../test/module/VacuumModuleTest.java | 91 +++++++++++++++++++ 11 files changed, 264 insertions(+), 27 deletions(-) create mode 100644 src/main/resources/META-INF/accesstransformer.cfg create mode 100644 src/test/java/me/desht/modularrouters/test/module/PlayerModuleTest.java create mode 100644 src/test/java/me/desht/modularrouters/test/module/VacuumModuleTest.java diff --git a/build.gradle b/build.gradle index 2dfa1c96..d68423ab 100644 --- a/build.gradle +++ b/build.gradle @@ -70,6 +70,15 @@ base { java { toolchain.languageVersion = JavaLanguageVersion.of(21) withSourcesJar() + + registerFeature('testUtils') { + usingSourceSet(sourceSets.test) + withSourcesJar() + } +} + +minecraft { + accessTransformers.file(project.file('src/main/resources/META-INF/accesstransformer.cfg')) } runs { @@ -180,6 +189,7 @@ publishing { } repositories { maven { + name 'local' url "file://${project.projectDir}/repo" } if (System.getenv("MODMAVEN_USER") != null) { 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 ee394ca4..d9704596 100644 --- a/src/main/java/me/desht/modularrouters/block/tile/ModularRouterBlockEntity.java +++ b/src/main/java/me/desht/modularrouters/block/tile/ModularRouterBlockEntity.java @@ -446,7 +446,7 @@ private boolean runAllModules(boolean powered, boolean pulsed) { for (CompiledIndexedModule cim : compiledModules) { CompiledModule cm = cim.compiledModule; - if (cm != null && cm.shouldExecute() && cm.getEnergyCost() <= getEnergyStorage().getEnergyStored() && cm.shouldExecute(powered, pulsed)) { + if (cm != null && cm.shouldExecute() && cm.getEnergyCost() <= getEnergyStorage().getEnergyStored() && cm.checkRedstone(powered, pulsed)) { var event = cm.getEvent(); if (event != null) { event.setExecuted(false); diff --git a/src/main/java/me/desht/modularrouters/logic/compiled/CompiledExtruderModule1.java b/src/main/java/me/desht/modularrouters/logic/compiled/CompiledExtruderModule1.java index 166ed0d0..9b0096c8 100644 --- a/src/main/java/me/desht/modularrouters/logic/compiled/CompiledExtruderModule1.java +++ b/src/main/java/me/desht/modularrouters/logic/compiled/CompiledExtruderModule1.java @@ -104,7 +104,7 @@ void tryPushEntities(Level world, BlockPos placePos, Direction facing) { } @Override - public boolean shouldExecute(boolean powered, boolean pulsed) { + public boolean checkRedstone(boolean powered, boolean pulsed) { 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 6f25a1d9..f8e6e8a3 100644 --- a/src/main/java/me/desht/modularrouters/logic/compiled/CompiledModule.java +++ b/src/main/java/me/desht/modularrouters/logic/compiled/CompiledModule.java @@ -325,7 +325,7 @@ public ModuleTarget getEffectiveTarget(ModularRouterBlockEntity router) { } @ApiStatus.OverrideOnly - public boolean shouldExecute(boolean powered, boolean pulsed) { + public boolean checkRedstone(boolean powered, boolean pulsed) { return getRedstoneBehaviour().shouldRun(powered, pulsed); } diff --git a/src/main/java/me/desht/modularrouters/logic/compiled/CompiledPlayerModule.java b/src/main/java/me/desht/modularrouters/logic/compiled/CompiledPlayerModule.java index ec30ebf6..caf17974 100644 --- a/src/main/java/me/desht/modularrouters/logic/compiled/CompiledPlayerModule.java +++ b/src/main/java/me/desht/modularrouters/logic/compiled/CompiledPlayerModule.java @@ -14,6 +14,7 @@ import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.util.StringRepresentable; +import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; @@ -75,7 +76,7 @@ public boolean execute(@Nonnull ModularRouterBlockEntity router) { case FROM_ROUTER -> { if (getFilter().test(bufferStack)) { if (getSection() == Section.ARMOR) { - return insertArmor(router, itemHandler, bufferStack); + return insertArmor(router, player, itemHandler, bufferStack); } else { int nToSend = getItemsPerTick(router); if (getRegulationAmount() > 0) { @@ -152,8 +153,8 @@ public Section getSection() { return settings.section; } - private boolean insertArmor(ModularRouterBlockEntity router, IItemHandler itemHandler, ItemStack armorStack) { - int slot = getSlotForArmorItem(armorStack); + private boolean insertArmor(ModularRouterBlockEntity router, Player player, IItemHandler itemHandler, ItemStack armorStack) { + int slot = getSlotForArmorItem(player, armorStack); if (slot >= 0 && itemHandler.getStackInSlot(slot).isEmpty()) { ItemStack extracted = router.getBuffer().extractItem(0, 1, false); if (extracted.isEmpty()) { @@ -166,13 +167,13 @@ private boolean insertArmor(ModularRouterBlockEntity router, IItemHandler itemHa } } - private int getSlotForArmorItem(ItemStack stack) { - return switch (stack.getEquipmentSlot()) { + private int getSlotForArmorItem(LivingEntity entity, ItemStack stack) { + return switch (entity.getEquipmentSlotForItem(stack)) { case HEAD -> 3; case CHEST -> 2; case LEGS -> 1; case FEET -> 0; - case null, default -> -1; + default -> -1; }; } diff --git a/src/main/java/me/desht/modularrouters/logic/compiled/CompiledVacuumModule.java b/src/main/java/me/desht/modularrouters/logic/compiled/CompiledVacuumModule.java index 7c260f67..b6e8d96b 100644 --- a/src/main/java/me/desht/modularrouters/logic/compiled/CompiledVacuumModule.java +++ b/src/main/java/me/desht/modularrouters/logic/compiled/CompiledVacuumModule.java @@ -94,6 +94,11 @@ public List setupTargets(ModularRouterBlockEntity router, ItemStac return List.of(new ModuleTarget(gPos, facing)); } + @Override // the target is computed programmatically, their validity is not relevant + protected boolean isTargetValid(ModularRouterBlockEntity router, ModuleTarget target) { + return true; + } + private boolean handleItemMode(ModularRouterBlockEntity router) { if (router.isBufferFull()) { return false; @@ -146,7 +151,7 @@ private boolean handleXpMode(ModularRouterBlockEntity router) { if (!inRouterStack.isEmpty() && !ItemStack.isSameItemSameComponents(inRouterStack, getXPCollectionType().getIcon())) { return false; } - spaceForXp = (inRouterStack.getMaxStackSize() - inRouterStack.getCount()) * getXPCollectionType().getXpRatio(); + spaceForXp = ((inRouterStack.isEmpty() ? getXPCollectionType().getIcon() : inRouterStack).getMaxStackSize() - inRouterStack.getCount()) * getXPCollectionType().getXpRatio(); } else { fluidHandler = getFluidReceiver(router); if (fluidHandler == null) { @@ -172,25 +177,32 @@ private boolean handleXpMode(ModularRouterBlockEntity router) { int initialSpaceForXp = spaceForXp; for (ExperienceOrb orb : orbs) { - if (orb.getValue() > spaceForXp) { - break; - } - if (xpCollectionType.isSolid()) { - xpBuffered += orb.getValue(); - if (xpBuffered > xpCollectionType.getXpRatio()) { - int count = xpBuffered / xpCollectionType.getXpRatio(); - ItemStack stack = xpCollectionType.getIcon().copyWithCount(count); - ItemStack excess = router.insertBuffer(stack); - xpBuffered -= stack.getCount() * xpCollectionType.getXpRatio(); - if (!excess.isEmpty()) { - InventoryUtils.dropItems(router.nonNullLevel(), Vec3.atCenterOf(router.getBlockPos()), excess); + var rate = getItemsPerTick(router); + while (orb.getValue() <= spaceForXp && rate > 0) { + if (xpCollectionType.isSolid()) { + xpBuffered += orb.getValue(); + if (xpBuffered > xpCollectionType.getXpRatio()) { + int count = xpBuffered / xpCollectionType.getXpRatio(); + ItemStack stack = xpCollectionType.getIcon().copyWithCount(count); + ItemStack excess = router.insertBuffer(stack); + xpBuffered -= stack.getCount() * xpCollectionType.getXpRatio(); + if (!excess.isEmpty()) { + InventoryUtils.dropItems(router.nonNullLevel(), Vec3.atCenterOf(router.getBlockPos()), excess); + } } + } else if (!doFluidXPFill(orb, fluidHandler)) { + spaceForXp = 0; + } + + spaceForXp -= orb.getValue(); + orb.count--; + rate--; + + if (orb.count <= 0) { + orb.remove(Entity.RemovalReason.DISCARDED); + break; } - } else if (!doFluidXPFill(orb, fluidHandler)) { - spaceForXp = 0; } - spaceForXp -= orb.getValue(); - orb.remove(Entity.RemovalReason.DISCARDED); } return initialSpaceForXp - spaceForXp > 0; diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg new file mode 100644 index 00000000..89bcbc33 --- /dev/null +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -0,0 +1 @@ +public net.minecraft.world.entity.ExperienceOrb count diff --git a/src/test/java/me/desht/modularrouters/test/ModularRoutersTest.java b/src/test/java/me/desht/modularrouters/test/ModularRoutersTest.java index bb59d0e5..182e2e7b 100644 --- a/src/test/java/me/desht/modularrouters/test/ModularRoutersTest.java +++ b/src/test/java/me/desht/modularrouters/test/ModularRoutersTest.java @@ -4,6 +4,7 @@ import net.neoforged.bus.api.IEventBus; import net.neoforged.fml.ModContainer; import net.neoforged.fml.common.Mod; +import net.neoforged.fml.loading.moddiscovery.locators.UserdevLocator; import net.neoforged.testframework.conf.ClientConfiguration; import net.neoforged.testframework.conf.Feature; import net.neoforged.testframework.conf.FrameworkConfiguration; @@ -15,6 +16,9 @@ @Mod("modularrouterstest") public class ModularRoutersTest { public ModularRoutersTest(ModContainer container, IEventBus eventBus) { + // we only set up our tests in modular routers test, and not when others use the test helpers + if (!(container.getModInfo().getOwningFile().getFile().getDiscoveryAttributes().locator() instanceof UserdevLocator)) return; + final MutableTestFramework framework = FrameworkConfiguration.builder(ResourceLocation.fromNamespaceAndPath(container.getNamespace(), "tests")) .clientConfiguration(() -> ClientConfiguration.builder() .toggleOverlayKey(GLFW.GLFW_KEY_J) diff --git a/src/test/java/me/desht/modularrouters/test/RouterTestHelper.java b/src/test/java/me/desht/modularrouters/test/RouterTestHelper.java index e82b0bb5..789af394 100644 --- a/src/test/java/me/desht/modularrouters/test/RouterTestHelper.java +++ b/src/test/java/me/desht/modularrouters/test/RouterTestHelper.java @@ -1,7 +1,6 @@ package me.desht.modularrouters.test; import me.desht.modularrouters.block.tile.ModularRouterBlockEntity; -import me.desht.modularrouters.config.ConfigHolder; import me.desht.modularrouters.container.handler.AugmentHandler; import me.desht.modularrouters.core.ModBlocks; import me.desht.modularrouters.core.ModDataComponents; @@ -16,16 +15,20 @@ import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.gametest.framework.GameTestInfo; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.item.ItemEntity; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.entity.ChestBlockEntity; +import net.minecraft.world.phys.Vec3; import net.neoforged.neoforge.items.IItemHandlerModifiable; import net.neoforged.testframework.gametest.ExtendedGameTestHelper; import java.util.List; import java.util.function.Consumer; import java.util.function.Supplier; +import java.util.function.UnaryOperator; import java.util.stream.IntStream; public class RouterTestHelper extends ExtendedGameTestHelper { @@ -59,7 +62,30 @@ public ChestBlockEntity placeChest(int x, int y, int z) { return getBlockEntity(x, y, z, ChestBlockEntity.class); } + public ItemEntity spawnItem(Item pItem, int count, Vec3 pPos) { + ServerLevel serverlevel = this.getLevel(); + Vec3 vec3 = this.absoluteVec(pPos); + ItemEntity itementity = new ItemEntity(serverlevel, vec3.x, vec3.y, vec3.z, new ItemStack(pItem, count)); + itementity.setDeltaMovement(0.0, 0.0, 0.0); + serverlevel.addFreshEntity(itementity); + return itementity; + } + + public ItemEntity spawnItem(Item pItem, int count, float pX, float pY, float pZ) { + return this.spawnItem(pItem, count, new Vec3(pX, pY, pZ)); + } + + public void assertStack(ItemStack stack, Item expectedItem, int expectedCount) { + assertValueEqual(stack.getItem(), expectedItem, "stack item"); + assertValueEqual(stack.getCount(), expectedCount, "stack count"); + } + public record RouterWrapper(ModularRouterBlockEntity router, RouterTestHelper helper) { + public RouterWrapper maxSpeed() { + addUpgrade(ModItems.SPEED_UPGRADE.toStack(9)); + return this; + } + public ItemStack addDirectionalModule(Supplier module, RelativeDirection direction) { var stack = module.get().getDefaultInstance(); stack.set(ModDataComponents.COMMON_MODULE_SETTINGS, new ModuleSettings( @@ -95,6 +121,15 @@ public ItemStack insertBuffer(ItemStack stack) { return stack; } + public ItemStack setBuffer(ItemStack stack) { + router.setBufferItemStack(stack); + return stack; + } + + public void modifyModuleSettings(int index, UnaryOperator mod) { + modifyModule(index, s -> s.set(ModDataComponents.COMMON_MODULE_SETTINGS, mod.apply(new ModuleSettingsBuilder(s.get(ModDataComponents.COMMON_MODULE_SETTINGS))).build())); + } + public void modifyModule(int index, Consumer mod) { var module = router.getModules().getStackInSlot(index).copy(); mod.accept(module); @@ -113,6 +148,16 @@ public ItemStack getBuffer() { return router.getBufferItemStack(); } + public void assertBuffer(Item item, int count) { + var buf = router.getBufferItemStack(); + helper.assertValueEqual(buf.getItem(), item, "router buffer item"); + helper.assertValueEqual(buf.getCount(), count, "router buffer item count"); + } + + public void assertBufferEmpty() { + helper.assertTrue(getBuffer().isEmpty(), "router buffer is not empty"); + } + public int routerTicks(int routerTicks) { return (20 - IntStream.range(0, router().getUpgradeSlotCount()) .mapToObj(router().getUpgrades()::getStackInSlot) @@ -120,4 +165,30 @@ public int routerTicks(int routerTicks) { .mapToInt(ItemStack::getCount).sum() * 2) * routerTicks; } } + + public static class ModuleSettingsBuilder { + private ModuleFlags flags; + private RelativeDirection facing; + private ModuleTermination termination; + private RedstoneBehaviour redstoneBehaviour; + private int regulatorAmount; + + public ModuleSettingsBuilder() {} + public ModuleSettingsBuilder(ModuleSettings settings) { + this.flags = settings.flags(); + this.facing = settings.facing(); + this.termination = settings.termination(); + this.redstoneBehaviour = settings.redstoneBehaviour(); + this.regulatorAmount = settings.regulatorAmount(); + } + + public ModuleSettingsBuilder facing(RelativeDirection direction) { + this.facing = direction; + return this; + } + + public ModuleSettings build() { + return new ModuleSettings(flags, facing, termination, redstoneBehaviour, regulatorAmount); + } + } } diff --git a/src/test/java/me/desht/modularrouters/test/module/PlayerModuleTest.java b/src/test/java/me/desht/modularrouters/test/module/PlayerModuleTest.java new file mode 100644 index 00000000..570ef8fe --- /dev/null +++ b/src/test/java/me/desht/modularrouters/test/module/PlayerModuleTest.java @@ -0,0 +1,47 @@ +package me.desht.modularrouters.test.module; + +import me.desht.modularrouters.core.ModDataComponents; +import me.desht.modularrouters.core.ModItems; +import me.desht.modularrouters.logic.compiled.CompiledPlayerModule; +import me.desht.modularrouters.logic.settings.TransferDirection; +import me.desht.modularrouters.test.RouterTestHelper; +import net.minecraft.gametest.framework.GameTest; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.component.ResolvableProfile; +import net.minecraft.world.level.GameType; +import net.neoforged.testframework.annotation.TestHolder; +import net.neoforged.testframework.gametest.EmptyTemplate; + +public class PlayerModuleTest { + @GameTest + @TestHolder + @EmptyTemplate(floor = true) + static void testPlayerModule(final RouterTestHelper helper) { + var router = helper.placeRouter(1, 2, 1).maxSpeed(); + var player = helper.makeTickingMockServerPlayerInCorner(GameType.SURVIVAL); + + player.setItemInHand(InteractionHand.OFF_HAND, new ItemStack(Items.APPLE, 10)); + var module = ModItems.PLAYER_MODULE.toStack(); + module.set(ModDataComponents.OWNER, new ResolvableProfile(player.getGameProfile())); + module.set(ModDataComponents.PLAYER_SETTINGS, new CompiledPlayerModule.PlayerSettings(TransferDirection.TO_ROUTER, CompiledPlayerModule.Section.OFFHAND)); + router.addModule(module); + + helper.startSequence() + .thenIdle(router.routerTicks(3)) + .thenExecute(() -> router.assertBuffer(Items.APPLE, 3)) + .thenExecute(() -> helper.assertStack(player.getItemInHand(InteractionHand.OFF_HAND), Items.APPLE, 7)) + + .thenExecute(() -> router.setBuffer(new ItemStack(Items.DIAMOND_CHESTPLATE))) + .thenExecute(() -> router.modifyModule(0, s -> s.set(ModDataComponents.PLAYER_SETTINGS, + new CompiledPlayerModule.PlayerSettings(TransferDirection.FROM_ROUTER, CompiledPlayerModule.Section.ARMOR)))) + + .thenIdle(router.routerTicks(1)) + + .thenExecute(router::assertBufferEmpty) + .thenExecute(() -> helper.assertStack(player.getItemBySlot(EquipmentSlot.CHEST), Items.DIAMOND_CHESTPLATE, 1)) + .thenSucceed(); + } +} diff --git a/src/test/java/me/desht/modularrouters/test/module/VacuumModuleTest.java b/src/test/java/me/desht/modularrouters/test/module/VacuumModuleTest.java new file mode 100644 index 00000000..364bd80a --- /dev/null +++ b/src/test/java/me/desht/modularrouters/test/module/VacuumModuleTest.java @@ -0,0 +1,91 @@ +package me.desht.modularrouters.test.module; + +import me.desht.modularrouters.core.ModDataComponents; +import me.desht.modularrouters.core.ModItems; +import me.desht.modularrouters.integration.XPCollection; +import me.desht.modularrouters.logic.compiled.CompiledVacuumModule; +import me.desht.modularrouters.logic.settings.RelativeDirection; +import me.desht.modularrouters.test.RouterTestHelper; +import net.minecraft.core.BlockPos; +import net.minecraft.gametest.framework.GameTest; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.item.Items; +import net.neoforged.testframework.annotation.TestHolder; +import net.neoforged.testframework.gametest.EmptyTemplate; + +public class VacuumModuleTest { + @GameTest + @TestHolder + @EmptyTemplate("13x3x13") + static void testVacuumModule(final RouterTestHelper helper) { + var router = helper.placeRouter(6, 1, 6); + + router.addModule(ModItems.VACUUM_MODULE.toStack()); + + helper.startSequence() + .thenExecute(() -> helper.spawnItem(Items.APPLE, 64, 6.5f, 2.5f, 6.5f)) + .thenIdle(router.routerTicks(2)) + .thenExecute(() -> router.assertBuffer(Items.APPLE, 2)) + + .thenExecute(() -> router.modifyAugments(0, u -> u.insertItem(0, ModItems.STACK_AUGMENT.toStack(2), false))) + .thenIdle(router.routerTicks(1)) + .thenExecute(() -> router.assertBuffer(Items.APPLE, 6)) + .thenExecute(() -> helper.assertItemEntityCountIs(Items.APPLE, new BlockPos(6, 2, 6), 0, 58)) + .thenSucceed(); + } + + @GameTest + @TestHolder + @EmptyTemplate(value = "5x3x5", floor = true) + static void testVacuumModuleRange(final RouterTestHelper helper) { + var router = helper.placeRouter(2, 2, 2).maxSpeed(); + + router.addModule(ModItems.VACUUM_MODULE.toStack()); + // Reduce range to 1 block + router.modifyAugments(0, u -> u.insertItem(0, ModItems.RANGE_DOWN_AUGMENT.toStack(5), false)); + + helper.startSequence() + .thenExecute(() -> helper.spawnItem(Items.APPLE, 64, 4.5f, 2.5f, 3.5f)) + .thenIdle(router.routerTicks(1)) + .thenExecute(router::assertBufferEmpty) // range does not suffice, so expect it not to have picked up the items + + // make the module direct towards the left so that the AABB moves and includes the items + .thenExecute(() -> router.modifyModuleSettings(0, s -> s.facing(RelativeDirection.LEFT))) // right is towards the structure block + + .thenIdle(router.routerTicks(2)) + .thenExecute(() -> router.assertBuffer(Items.APPLE, 2)) + .thenSucceed(); + } + + @GameTest + @TestHolder + @EmptyTemplate(floor = true) + static void testVacuumModuleXp(final RouterTestHelper helper) { + var router = helper.placeRouter(1, 2, 1).maxSpeed(); + + var stack = ModItems.VACUUM_MODULE.toStack(); + + // bottle o enchanting per 7 xp + stack.set(ModDataComponents.VACUUM_SETTINGS, new CompiledVacuumModule.VacuumSettings(false, XPCollection.XPCollectionType.BOTTLE_O_ENCHANTING)); + router.addModule(stack); + router.modifyAugments(0, u -> { + // Reduce range to 1 block + u.insertItem(0, ModItems.RANGE_DOWN_AUGMENT.toStack(5), false); + + // Enable XP collection + u.insertItem(1, ModItems.XP_VACUUM_AUGMENT.toStack(), false); + }); + + var orb = helper.spawn(EntityType.EXPERIENCE_ORB, 1.5f, 2.5f, 2.5f); + // not perfectly divisible by 7, but we do have losses when converting so that's intended + orb.value = 16; + orb.count = 4; + helper.startSequence() + .thenIdle(router.routerTicks(3)) + + .thenExecute(() -> router.assertBuffer(Items.EXPERIENCE_BOTTLE, 6)) // 3 orbs * 2 bottles (14 xp value) + .thenExecute(() -> helper.assertValueEqual(orb.count, 1, "orb count")) // should have decreased the count of the orb by 3 + .thenExecute(() -> helper.assertValueEqual(orb.value, 16, "orb value")) // but don't touch the value + .thenSucceed(); + } +} From 6e771fcfe3e7c0cc8f8058adc9549682f868d2a7 Mon Sep 17 00:00:00 2001 From: Matyrobbrt Date: Sun, 24 Nov 2024 14:45:25 +0200 Subject: [PATCH 10/10] Remove unused stack param --- .../desht/modularrouters/item/module/ITargetedModule.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/me/desht/modularrouters/item/module/ITargetedModule.java b/src/main/java/me/desht/modularrouters/item/module/ITargetedModule.java index 68e09ee4..563c699f 100644 --- a/src/main/java/me/desht/modularrouters/item/module/ITargetedModule.java +++ b/src/main/java/me/desht/modularrouters/item/module/ITargetedModule.java @@ -68,7 +68,7 @@ static Set getTargets(ItemStack stack, boolean checkBlockName) { for (int i = 0; i < targets.size() && result.size() < max; i++) { var target = targets.get(i); if (checkBlockName) { - var newTarget = updateTargetBlockName(stack, target); + var newTarget = updateTargetBlockName(target); if (newTarget != target) update = true; target = newTarget; } @@ -78,7 +78,7 @@ static Set getTargets(ItemStack stack, boolean checkBlockName) { } if (update) { - stack.set(ModDataComponents.MODULE_TARGET_LIST, new ModuleTargetList(List.copyOf(result))); + setTargets(stack, result); } return result; @@ -101,7 +101,7 @@ static boolean canSelectTarget(UseOnContext context) { return NeoForge.EVENT_BUS.post(new AddModuleTargetEvent((ModuleItem) module, context, ((ITargetedModule) module).isValidTarget(context))).isValid(); } - private static ModuleTarget updateTargetBlockName(ItemStack stack, ModuleTarget target) { + private static ModuleTarget updateTargetBlockName(ModuleTarget target) { ServerLevel level = MiscUtil.getWorldForGlobalPos(target.gPos); BlockPos pos = target.gPos.pos(); if (level != null && level.getChunkSource().hasChunk(pos.getX() >> 4, pos.getZ() >> 4)) {