Compare commits

...

3 Commits

9 changed files with 354 additions and 84 deletions

3
.gitignore vendored

@ -4,7 +4,8 @@
*.iml
*.ipr
*.iws
#VS Code things
.vscode/
# IntelliJ
out/
# mpeltonen/sbt-idea plugin

71
README.md Normal file

@ -0,0 +1,71 @@
# RegexingHoppers
RegexingHoppers is a Minecraft mod that enhances the functionality of hoppers by allowing the use of regular expressions (regex) to filter items passing through them. This mod adds a new layer of customization and control to item sorting and transportation systems in Minecraft.
## Features
- Use custom names for hoppers as regex patterns to filter items
- Three new commands for testing and managing regex patterns
- Configurable permission levels for commands
## Installation
1. Make sure you have Fabric Loader installed
2. Download the latest version of RegexingHoppers from the releases page
3. Place the downloaded .jar file in your Minecraft mods folder
4. Launch Minecraft with the Fabric profile
## Usage
### Regex Filtering
To use regex filtering on a hopper:
1. Rename the hopper using an anvil with your desired regex pattern
2. Place the renamed hopper in your item system
3. Items matching the regex pattern will be allowed through, while others will be filtered out
### Commands
RegexingHoppers adds the following commands:
1. `/regexhoppers test <regex>`: Test a regex pattern against all item names
2. `/regexhoppers list <regex>`: List all items matching the given regex pattern
3. `/regexhoppers hand`: Display the name of the item in your main hand
By default, all players can use these commands. You can adjust the permission levels in the configuration file.
## Configuration
The mod includes a configuration file (`regexhoppers.json`) located in the `config` folder. You can adjust the permission levels required for each command:
```json
{
"permissions": [
{
"name": "regexhoppers.command.test",
"level": 0
},
{
"name": "regexhoppers.command.list",
"level": 0
},
{
"name": "regexhoppers.command.hand",
"level": 0
}
]
}
```
Adjust the `level` value for each command to set the required permission level. A level of 0 allows all players to use the command.
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
## Support
If you encounter any issues or have any questions, please open an issue on our GitHub repository.
Happy filtering!

6
TODO.md Normal file

@ -0,0 +1,6 @@
# TODO File
## Ideas
- Add a not match system
- For example `not .*iron.*` will let all items, except iron ones, literaly the reverse of `.*iron.*`. It can be usefull to avoid too complexe regex

@ -1,20 +1,14 @@
package systems.brn.regexinghoppers;
import net.fabricmc.api.ModInitializer;
import net.minecraft.block.entity.HopperBlockEntity;
import net.minecraft.inventory.Inventory;
import net.minecraft.screen.NamedScreenHandlerFactory;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import systems.brn.regexinghoppers.mixin.HopperBlockEntityAccessor;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import systems.brn.regexinghoppers.commands.RegexHoppersCommands;
import systems.brn.regexinghoppers.config.ModConfig;
public class RegexingHoppers implements ModInitializer {
// This logger is used to write text to the console and the log file.
// It is considered best practice to use your mod id as the logger's name.
// That way, it's clear which mod wrote info, warnings, and errors.
public static final Logger LOGGER = LoggerFactory.getLogger("RegexingHoppers");
@Override
@ -22,55 +16,10 @@ public class RegexingHoppers implements ModInitializer {
// This code runs as soon as Minecraft is in a mod-load-ready state.
// However, some things (like resources) may still be uninitialized.
// Proceed with mild caution.
ModConfig.getInstance();
LOGGER.debug("RegexingHoppers initialized!");
}
public static boolean shouldNotMove(Inventory hopper, String itemName) {
// Log entering the method with given parameters
LOGGER.debug("Entering shouldNotMove with itemName: {}", itemName);
if (hopper instanceof NamedScreenHandlerFactory factory) {
String customName = factory.getDisplayName().getLiteralString();
// Log the custom name used for matching
LOGGER.debug("Custom regex pattern from hopper: {}", customName);
if (customName != null && !customName.isEmpty()) {
if (hopper instanceof HopperBlockEntity) {
HopperBlockEntityAccessor hopperAccessor = (HopperBlockEntityAccessor) hopper;
if (hopperAccessor.getTransferCooldown() > 1) {
return true;
}
}
try {
Pattern pattern = Pattern.compile(customName);
Matcher matcher = pattern.matcher(itemName);
// Log the result of the regex matching
boolean matches = matcher.matches();
LOGGER.debug("Regex matching result: {}", matches);
if (!matches) {
if (hopper instanceof HopperBlockEntity) {
HopperBlockEntityAccessor hopperAccessor = (HopperBlockEntityAccessor) hopper;
hopperAccessor.setTransferCooldown(8);
}
}
return !matches;
} catch (PatternSyntaxException e) {
// Log exception if regex pattern is invalid
LOGGER.debug("Invalid regex pattern: {}", customName, e);
}
} else {
// Log case when custom name is null or empty
LOGGER.debug("Custom name is null or empty, not performing regex matching.");
}
} else {
// Log if hopper is not an instance of NamedScreenHandlerFactory
LOGGER.debug("Hopper is not an instance of NamedScreenHandlerFactory.");
}
// Default return value in case no conditions are met
return false;
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> {
RegexHoppersCommands.register(dispatcher);
});
}
}

@ -0,0 +1,98 @@
package systems.brn.regexinghoppers.commands;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import systems.brn.regexinghoppers.config.ModConfig;
import systems.brn.regexinghoppers.util.Tools;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.registry.Registries;
import java.util.regex.PatternSyntaxException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static net.minecraft.server.command.CommandManager.argument;
import static net.minecraft.server.command.CommandManager.literal;
public class RegexHoppersCommands {
public static final Logger LOGGER = LoggerFactory.getLogger("RegexingHoppers");
// TODO: Add config file for permissions system
public static void register(CommandDispatcher<ServerCommandSource> dispatcher) {
dispatcher.register(literal("regexhoppers")
.then(literal("test")
.requires(source -> source.hasPermissionLevel(ModConfig.getInstance().getTestPermission().level))
.then(argument("regex", StringArgumentType.greedyString())
.executes(context -> executeMatch(context.getSource(), StringArgumentType.getString(context, "regex")))))
.then(literal("list")
.requires(source -> source.hasPermissionLevel(ModConfig.getInstance().getListPermission().level))
.then(argument("regex", StringArgumentType.greedyString())
.executes(context -> executeList(context.getSource(), StringArgumentType.getString(context, "regex")))))
.then(literal("hand")
.requires(source -> source.hasPermissionLevel(ModConfig.getInstance().getHandPermission().level))
.executes(context -> executeHand(context.getSource()))));
}
private static int executeMatch(ServerCommandSource source, final String regexString) {
try {
int count = 0;
for (Identifier id : Registries.ITEM.getIds()) {
Item item = Registries.ITEM.get(id);
String itemName = Tools.getNameOf(item);
if (Tools.itemMatch(itemName, regexString)) {
count++;
}
}
final int total = count;
source.sendFeedback(() -> Text.literal(regexString + " match with " + total + " items"), false);
return 1;
} catch (PatternSyntaxException e) {
return 0;
}
}
private static int executeList(ServerCommandSource source, final String regexString) {
try {
int count = 0;
String allMatchs = "";
for (Identifier id : Registries.ITEM.getIds()) {
Item item = Registries.ITEM.get(id);
String itemName = item.getName().getString().toLowerCase();
if (Tools.itemMatch(itemName, regexString)) {
count++;
allMatchs += itemName + "\n";
}
}
final int total = count;
final String totalAllMatchs = allMatchs;
source.sendFeedback(() -> Text.literal("Full match list: \n" + totalAllMatchs + regexString + " match with " + total + " items\n"), false);
return 1;
} catch (PatternSyntaxException e) {
return 0;
}
}
private static int executeHand(ServerCommandSource source) {
ServerPlayerEntity player = source.getPlayer();
if (player != null) {
ItemStack heldItem = player.getMainHandStack();
if(heldItem != null) {
source.sendFeedback(() -> Text.literal("Your main hand contain: '" + Tools.getNameOf(heldItem.getItem()) + "'" ), false);
return 1;
}
source.sendFeedback(() -> Text.literal("No item found"), false);
}
return 0;
}
}

@ -0,0 +1,86 @@
package systems.brn.regexinghoppers.config;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import net.fabricmc.loader.api.FabricLoader;
import java.io.*;
import java.nio.file.Path;
import java.util.ArrayList;
public class ModConfig {
private static final String CONFIG_FILE = "regexhoppers.json";
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
private static String[] permissionsName = {
"regexhoppers.command.test",
"regexhoppers.command.list",
"regexhoppers.command.hand"
};
public ArrayList<PermissionEntry> permissions;
private static ModConfig INSTANCE;
public static ModConfig getInstance() {
if (INSTANCE == null) {
INSTANCE = load();
}
return INSTANCE;
}
private static ModConfig load() {
Path configPath = FabricLoader.getInstance().getConfigDir().resolve(CONFIG_FILE);
if (configPath.toFile().exists()) {
try (Reader reader = new FileReader(configPath.toFile())) {
ModConfig mconfig = GSON.fromJson(reader, ModConfig.class);
mconfig.checkConfigFile();
return mconfig;
} catch (IOException e) {
e.printStackTrace();
}
}
ModConfig config = new ModConfig();
config.save();
return config;
}
public void save() {
// Should only run on the first time to generate the config file, or if file isn't loadable / broken
this.permissions = new ArrayList<>();
for (String permission : permissionsName) {
this.permissions.add(new PermissionEntry(permission, 0));
}
Path configPath = FabricLoader.getInstance().getConfigDir().resolve(CONFIG_FILE);
try (Writer writer = new FileWriter(configPath.toFile())) {
GSON.toJson(this, writer);
} catch (IOException e) {
e.printStackTrace();
}
}
private void checkConfigFile() {
if (this.permissions.size() < permissionsName.length) {
this.save();
}
}
private PermissionEntry getIndex(int index) {
this.checkConfigFile();
return this.permissions.get(index);
}
public PermissionEntry getTestPermission() {
return this.getIndex(0);
}
public PermissionEntry getListPermission() {
return this.permissions.get(1);
}
public PermissionEntry getHandPermission() {
return this.permissions.get(2);
}
}

@ -0,0 +1,11 @@
package systems.brn.regexinghoppers.config;
public class PermissionEntry {
public String name;
public int level;
public PermissionEntry(String name, int level) {
this.name = name;
this.level = level;
}
}

@ -3,40 +3,32 @@ package systems.brn.regexinghoppers.mixin;
import net.minecraft.block.entity.HopperBlockEntity;
import net.minecraft.inventory.Inventory;
import net.minecraft.item.ItemStack;
import net.minecraft.registry.Registries;
import net.minecraft.screen.NamedScreenHandlerFactory;
import net.minecraft.util.math.Direction;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import systems.brn.regexinghoppers.RegexingHoppers;
import systems.brn.regexinghoppers.util.Tools;
@Mixin(HopperBlockEntity.class)
public class RegexingHopperMixin {
@Inject(method = "transfer(Lnet/minecraft/inventory/Inventory;Lnet/minecraft/inventory/Inventory;Lnet/minecraft/item/ItemStack;ILnet/minecraft/util/math/Direction;)Lnet/minecraft/item/ItemStack;",
at = @At(value = "HEAD"),
cancellable = true
)
private static void processContainer(Inventory from, Inventory _to, ItemStack stack, int slot, Direction side, CallbackInfoReturnable<ItemStack> cir) {
final String itemName = Registries.ITEM.getId(stack.getItem()).toString();
if(from != null) {
if (RegexingHoppers.shouldNotMove(from, itemName)) {
cir.setReturnValue(stack);
if(from instanceof HopperBlockEntity) {
((HopperBlockEntityAccessor) from).setTransferCooldown(8);
}
return;
}
@Inject(method = "canExtract", at = @At(value = "HEAD"), cancellable = true)
private static void canExtractReplacer(Inventory outputInventory, Inventory inputInventory, ItemStack stack,
int slot, Direction side, CallbackInfoReturnable<Boolean> cir) {
if (outputInventory == null) {
return;
}
if(_to != null) {
if (RegexingHoppers.shouldNotMove(_to, itemName)) {
cir.setReturnValue(stack);
if(_to instanceof HopperBlockEntity) {
((HopperBlockEntityAccessor) _to).setTransferCooldown(8);
}
return;
if (outputInventory instanceof NamedScreenHandlerFactory factory) {
String customName = factory.getDisplayName().getLiteralString();
if (customName != null) {
Boolean canExtract = Tools.shouldMove(outputInventory, stack.getItem());
cir.setReturnValue(canExtract);
}
}
}
}

@ -0,0 +1,56 @@
package systems.brn.regexinghoppers.util;
import java.util.regex.PatternSyntaxException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.minecraft.block.entity.HopperBlockEntity;
import net.minecraft.inventory.Inventory;
import net.minecraft.item.Item;
import net.minecraft.screen.NamedScreenHandlerFactory;
import systems.brn.regexinghoppers.mixin.HopperBlockEntityAccessor;
public class Tools {
public static final Logger LOGGER = LoggerFactory.getLogger("RegexingHoppers");
public static boolean itemMatch(String itemName, String regex) {
return itemName.matches(regex);
}
public static String getNameOf(Item item) {
return item.getName().getString().toLowerCase();
}
public static boolean shouldMove(Inventory hopper, Item item) {
if (!(hopper instanceof NamedScreenHandlerFactory)) {
// Log if hopper is not an instance of NamedScreenHandlerFactory
LOGGER.debug("Hopper is not an instance of NamedScreenHandlerFactory.");
return true;
}
NamedScreenHandlerFactory factory = (NamedScreenHandlerFactory) hopper;
String customName = factory.getDisplayName().getLiteralString();
if (customName == null || customName.isEmpty()) {
// Log case when custom name is null or empty
LOGGER.debug("Custom name is null or empty, not performing regex matching.");
return true;
}
// TODO: Test if this code still needed
if (hopper instanceof HopperBlockEntity) {
HopperBlockEntityAccessor hopperAccessor = (HopperBlockEntityAccessor) hopper;
if (hopperAccessor.getTransferCooldown() > 1) {
return false;
}
}
try {
return Tools.itemMatch(getNameOf(item), customName);
} catch (PatternSyntaxException e) {
// Log exception if regex pattern is invalid
LOGGER.debug("Invalid regex pattern: {}", customName, e);
}
return true;
}
}