diff --git a/README.md b/README.md index 40eacb1..8c72dd1 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ A sample file looks like this: ```json { - + "LoadPoolsAtRuntime": false, "Names": { "Common": { "MinRolls": 1, @@ -59,6 +59,12 @@ A sample file looks like this: } ``` +`LoadPoolsAtRuntime` - decides whether the pools are loaded when Loot Table Loading happens for +the world or whether to load them when a chest is opened for the first time. Default +value is `false` which means the pools are loaded when the world is loaded. +Being `false` might make the mod compatible with other mods. Making it +`true` might make the mod incompatible with other mods. + `Names` are the names used to identify the kind of loot. This can be anything, but whatever they are, they must be the same on `ChestDefinitions` and `LootDefinitions`. - `MinRolls` and `MaxRolls` are the number of rolls to do for that rarity pool. Choosing diff --git a/gradle.properties b/gradle.properties index 769ad86..760583d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,13 +4,13 @@ org.gradle.jvmargs=-Xmx1G # Fabric Properties # check these on https://fabricmc.net/develop minecraft_version=1.18.2 - yarn_mappings=1.18.2+build.1 - loader_version=0.13.3 + yarn_mappings=1.18.2+build.3 + loader_version=0.14.4 # Mod Properties - mod_version = 1.0.3 + mod_version = 1.0.4 maven_group = com.github.levoment.chestlootmodifier archives_base_name = [1.18.2] Chest Loot Modifier # Dependencies - fabric_version=0.47.8+1.18.2 + fabric_version=0.51.1+1.18.2 diff --git a/src/main/java/com/github/levoment/chestlootmodifier/ChestLootModifierMod.java b/src/main/java/com/github/levoment/chestlootmodifier/ChestLootModifierMod.java index 39223b3..03dca7f 100644 --- a/src/main/java/com/github/levoment/chestlootmodifier/ChestLootModifierMod.java +++ b/src/main/java/com/github/levoment/chestlootmodifier/ChestLootModifierMod.java @@ -1,14 +1,35 @@ package com.github.levoment.chestlootmodifier; import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.loot.v1.FabricLootPoolBuilder; +import net.fabricmc.fabric.api.loot.v1.FabricLootSupplierBuilder; +import net.fabricmc.fabric.api.loot.v1.event.LootTableLoadingCallback; +import net.minecraft.item.Item; +import net.minecraft.loot.LootManager; +import net.minecraft.loot.LootPool; +import net.minecraft.loot.LootTable; +import net.minecraft.loot.entry.ItemEntry; +import net.minecraft.loot.function.SetCountLootFunction; +import net.minecraft.loot.provider.number.ConstantLootNumberProvider; +import net.minecraft.loot.provider.number.UniformLootNumberProvider; +import net.minecraft.util.Identifier; +import net.minecraft.util.InvalidIdentifierException; +import net.minecraft.util.registry.Registry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + public class ChestLootModifierMod 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("chestlootmodifier"); + private boolean addPool = false; + private LootTable chestLootModifierModModifiedLootTable; @Override public void onInitialize() { @@ -16,5 +37,123 @@ public void onInitialize() { ConfigManager.createConfigFile(); // Read the config file if it exists ConfigManager.readConfigFile(); + + + + LootTableLoadingCallback.EVENT.register((resourceManager, manager, id, supplier, setter) -> { + this.addPool = false; + // Regex to match everything before a parenthesis + Pattern beforeParenthesisPattern = Pattern.compile("^.*?(?=\\()", Pattern.CASE_INSENSITIVE); + Pattern betweenParenthesisPattern = Pattern.compile("(?<=\\().*?(?=\\))", Pattern.CASE_INSENSITIVE); + // Create the config file if it doesn't exist + ConfigManager.createConfigFile(); + // Read the config file + ConfigManager.readConfigFile(); + + // If the configuration was loaded successfully + if (ConfigManager.SUCCESSFULLY_LOADED_CONFIG) { + // Return if LoadPoolsAtRuntime is false + if (ConfigManager.CURRENT_CONFIG.loadPoolsAtRuntime()) return; + + ConfigManager.CURRENT_CONFIG.ChestDefinitions.forEach((key, chestIDs) -> { + // If the chest ID matches the current id being registered + if (chestIDs.contains(id.toString())) { + // Check if there is a loot definition for the chest + if (ConfigManager.CURRENT_CONFIG.LootDefinitions.containsKey(key)) { + // Get the Loot Name + Map rarityObjects = ConfigManager.CURRENT_CONFIG.getNames(); + // Create variables for the min and max roll for this rarity pool + Integer rarityMinRolls = null; + Integer rarityMaxRolls = null; + if (rarityObjects.containsKey(key)) { + rarityMinRolls = rarityObjects.get(key).getMinRolls(); + rarityMaxRolls = rarityObjects.get(key).getMaxRolls(); + + + } else { + ChestLootModifierMod.LOGGER.error("[Chest Loot Modifier Mod] The provided rarity named: '" + key + "' in LootDefinitions could not be found in 'Names' of the json config."); + addPool = false; + } + + // Build the pool and add it + List lootList = ConfigManager.CURRENT_CONFIG.LootDefinitions.get(key); + // Check if the list is not empty + if (lootList.size() > 0) { + LootPool currentLootPool = FabricLootPoolBuilder.builder().build(); + // Go through all the loot definitions for this chest in the config file + for (String itemID : lootList) { + + // Try to match the item part + Matcher matcher = beforeParenthesisPattern.matcher(itemID); + if (matcher.find()) { + String wholeMatch = matcher.group(); + // Try to get the item from the identifier + try { + // Try to get the given item + Item currentItem = Registry.ITEM.get(new Identifier(matcher.group())); + // Get the part of the percentage and amount conditions for the item + Matcher numberMatcher = betweenParenthesisPattern.matcher(itemID); + if (numberMatcher.find()) { + String firstMatch = numberMatcher.group(0); + String secondMatch = null; + if (numberMatcher.find()) { + secondMatch = numberMatcher.group(0); + } + + if (firstMatch == null || firstMatch.isBlank() || secondMatch == null || secondMatch.isBlank()) { + ChestLootModifierMod.LOGGER.error("[Chest Loot Modifier Mod] Item: '" + itemID + "' doesn't have a properly formatted number " + + "for the percentage loot chance and or amount for the item between parenthesis. " + + "Both, the percentage and number, must be integers. An example would be: minecraft:stone_sword(45)(1). Where 45 " + + "would be 45% chance of appearing among other loot in the chest and 1 is the amount of stone swords to put on the chest " + + "should a stone word appear on it."); + addPool = false; + } else { + // Try to get the item count + try { + int itemCount = Integer.parseInt(firstMatch); + // Try to get the weight of the item + try { + int itemWeight = Integer.parseInt(secondMatch); + // Create the pool builder + currentLootPool = FabricLootPoolBuilder.of(currentLootPool). + withEntry(ItemEntry.builder(currentItem).weight(itemWeight).build()) + .withFunction(SetCountLootFunction.builder(ConstantLootNumberProvider.create(itemCount)).build()) + .rolls(UniformLootNumberProvider.create(rarityMinRolls, rarityMaxRolls)) + .build(); + addPool = true; + } catch (NumberFormatException numberFormatException) { + ChestLootModifierMod.LOGGER.error("[Chest Loot Modifier Mod] The provided number: '" + secondMatch + "' in " + itemID + " could not be converted to an integer."); + addPool = false; + } + + } catch (NumberFormatException numberFormatException) { + ChestLootModifierMod.LOGGER.error("[Chest Loot Modifier Mod] The provided number: '" + firstMatch + "' in " + itemID + " could not be converted to an integer."); + addPool = false; + } + } + } else { + ChestLootModifierMod.LOGGER.error("[Chest Loot Modifier Mod] Item: '" + wholeMatch + "' is missing a percentage loot chance and or amount for the item between parenthesis."); + addPool = false; + } + } catch (InvalidIdentifierException invalidIdentifierException) { + ChestLootModifierMod.LOGGER.error("[Chest Loot Modifier Mod] The game could not find the item with identifier: '" + wholeMatch + "'"); + addPool = false; + } + } + } + // Add the item to the pool of items for the current chest + if (this.addPool) supplier.withPool(currentLootPool).build(); + } else { + ChestLootModifierMod.LOGGER.error("[Chest Loot Modifier Mod] There is no loot defined for " + key + " in the config file LootDefinition object"); + addPool = false; + } + } else { + ChestLootModifierMod.LOGGER.error("[Chest Loot Modifier Mod] There is no loot defined for " + key + " in the config file LootDefinition object"); + addPool = false; + } + } + }); + } + }); } } diff --git a/src/main/java/com/github/levoment/chestlootmodifier/ConfigManager.java b/src/main/java/com/github/levoment/chestlootmodifier/ConfigManager.java index 0c83e46..bc61501 100644 --- a/src/main/java/com/github/levoment/chestlootmodifier/ConfigManager.java +++ b/src/main/java/com/github/levoment/chestlootmodifier/ConfigManager.java @@ -25,7 +25,7 @@ public static void createConfigFile() { try { // Config file string String configFileText = "{\n" + - "\n" + + " \"LoadPoolsAtRuntime\": false,\n" + " \"Names\": {\n" + " \"Common\": {\n" + " \"MinRolls\": 1,\n" + @@ -44,7 +44,7 @@ public static void createConfigFile() { " \"MaxRolls\": 4\n" + " }\n" + " },\n" + - " \n" + + "\n" + " \"ChestDefinitions\": {\n" + " \"Common\": [],\n" + " \"Uncommon\": [],\n" + diff --git a/src/main/java/com/github/levoment/chestlootmodifier/ConfigurationObject.java b/src/main/java/com/github/levoment/chestlootmodifier/ConfigurationObject.java index 7c52e83..cba5bb3 100644 --- a/src/main/java/com/github/levoment/chestlootmodifier/ConfigurationObject.java +++ b/src/main/java/com/github/levoment/chestlootmodifier/ConfigurationObject.java @@ -5,16 +5,26 @@ public class ConfigurationObject { + public boolean LoadPoolsAtRuntime; public Map Names; public Map> ChestDefinitions; public Map> LootDefinitions; - public ConfigurationObject(Map names, Map> chestDefinitions, Map> lootDefinitions) { + public ConfigurationObject(boolean loadPoolsAtRuntime, Map names, Map> chestDefinitions, Map> lootDefinitions) { + this.LoadPoolsAtRuntime = loadPoolsAtRuntime; this.Names = names; this.ChestDefinitions = chestDefinitions; this.LootDefinitions = lootDefinitions; } + public boolean loadPoolsAtRuntime() { + return LoadPoolsAtRuntime; + } + + public void setLoadPoolsAtRuntime(boolean loadPoolsAtRuntime) { + LoadPoolsAtRuntime = loadPoolsAtRuntime; + } + public Map getNames() { return Names; } diff --git a/src/main/java/com/github/levoment/chestlootmodifier/mixins/LootableContainerBlockEntityMixin.java b/src/main/java/com/github/levoment/chestlootmodifier/mixins/LootableContainerBlockEntityMixin.java index c594979..9988a7d 100644 --- a/src/main/java/com/github/levoment/chestlootmodifier/mixins/LootableContainerBlockEntityMixin.java +++ b/src/main/java/com/github/levoment/chestlootmodifier/mixins/LootableContainerBlockEntityMixin.java @@ -55,6 +55,9 @@ public void checkLootInteractionMixinCallback(PlayerEntity player, CallbackInfo // If the configuration was loaded successfully if (ConfigManager.SUCCESSFULLY_LOADED_CONFIG) { + // Return if LoadPoolsAtRuntime is false + if (!ConfigManager.CURRENT_CONFIG.loadPoolsAtRuntime()) return; + ConfigManager.CURRENT_CONFIG.ChestDefinitions.forEach((key, chestIDs) -> { // If the chest ID matches the current id being registered if (chestIDs.contains(lootTableId.toString())) { @@ -68,8 +71,6 @@ public void checkLootInteractionMixinCallback(PlayerEntity player, CallbackInfo if (rarityObjects.containsKey(key)) { rarityMinRolls = rarityObjects.get(key).getMinRolls(); rarityMaxRolls = rarityObjects.get(key).getMaxRolls(); - - } else { ChestLootModifierMod.LOGGER.error("[Chest Loot Modifier Mod] The provided rarity named: '" + key + "' in LootDefinitions could not be found in 'Names' of the json config."); } @@ -154,6 +155,11 @@ public void checkLootInteractionMixinCallback(PlayerEntity player, CallbackInfo @ModifyVariable(method = "checkLootInteraction(Lnet/minecraft/entity/player/PlayerEntity;)V", at = @At(value = "STORE", id = "lootTable")) public LootTable modifyLootTable(LootTable lootTable) { + // If the configuration was loaded successfully + if (ConfigManager.SUCCESSFULLY_LOADED_CONFIG) { + // Return if LoadPoolsAtRuntime is false + if (!ConfigManager.CURRENT_CONFIG.loadPoolsAtRuntime()) return lootTable; + } if (this.addPool) { return this.chestLootModifierModModifiedLootTable; } else {