diff --git a/examples/loot.groovy b/examples/loot.groovy new file mode 100644 index 000000000..ad3eb5fc6 --- /dev/null +++ b/examples/loot.groovy @@ -0,0 +1,79 @@ + +def pyramidLootTable = loot.getTable("minecraft:chests/stronghold_library") + +pyramidLootTable.removePool("main") + +pyramidLootTable.addPool( + loot.poolBuilder("main") + .entry( + loot.entryBuilder("minecraft:diamond_block") + .item(item("minecraft:diamond_block")) + .weight(1) + .quality(1) + .build()) + .randomChance(1.0f) + .rollsRange(1.0f, 3.0f) + .bonusRollsRange(0.0f, 0.0f) + .build() +) + + + + +def pyramidLootTable2 = loot.getTable("minecraft:chests/stronghold_corridor") + +pyramidLootTable2.removePool("main") + +pyramidLootTable2.addPool( + loot.poolBuilder("main") + .entry( + loot.entryBuilder("minecraft:diamond_block") + .item(item("minecraft:diamond_block")) + .weight(1) + .quality(1) + .build()) + .randomChance(1.0f) + .rollsRange(1.0f, 3.0f) + .bonusRollsRange(0.0f, 0.0f) + .build() +) + +def chickenLootTable = loot.getTable("minecraft:entities/chicken") + +chickenLootTable.removePool("main") + +chickenLootTable.addPool( + loot.poolBuilder("main").entry( + loot.entryBuilder("minecraft:pumpkin") + .item(item("minecraft:pumpkin")) + .weight(1) + .quality(1) + .build() + ) + .randomChance(1.0f) + .rollsRange(1.0f, 3.0f) + .bonusRollsRange(0.0f, 0.0f) + .build() +) + +def zombieLootTable = loot.getTable("minecraft:entities/zombie") + +zombieLootTable.removePool("main") + +zombieLootTable.addPool( + loot.poolBuilder("main").entry( + loot.entryBuilder("minecraft:potato") + .item(item("minecraft:potato")) + .weight(1) + .quality(1) + .smelt() + .build() + ) + .randomChance(1.0f) + .killedByNonPlayer() + .rollsRange(1.0f, 3.0f) + .bonusRollsRange(0.0f, 0.0f) + .build() +) + +loot.printEntries() \ No newline at end of file diff --git a/src/main/java/com/cleanroommc/groovyscript/GroovyScript.java b/src/main/java/com/cleanroommc/groovyscript/GroovyScript.java index b440f53b8..c3b8e900f 100644 --- a/src/main/java/com/cleanroommc/groovyscript/GroovyScript.java +++ b/src/main/java/com/cleanroommc/groovyscript/GroovyScript.java @@ -4,10 +4,13 @@ import com.cleanroommc.groovyscript.brackets.BracketHandlerManager; import com.cleanroommc.groovyscript.command.CustomClickAction; import com.cleanroommc.groovyscript.command.GSCommand; +import com.cleanroommc.groovyscript.compat.loot.Loot; import com.cleanroommc.groovyscript.compat.content.GroovyResourcePack; import com.cleanroommc.groovyscript.compat.mods.ModSupport; import com.cleanroommc.groovyscript.compat.mods.tinkersconstruct.TinkersConstruct; import com.cleanroommc.groovyscript.compat.vanilla.VanillaModule; +import com.cleanroommc.groovyscript.core.mixin.loot.LootPoolAccessor; +import com.cleanroommc.groovyscript.core.mixin.loot.LootTableAccessor; import com.cleanroommc.groovyscript.core.mixin.DefaultResourcePackAccessor; import com.cleanroommc.groovyscript.event.EventHandler; import com.cleanroommc.groovyscript.helper.JsonHelper; @@ -34,6 +37,7 @@ import net.minecraft.util.text.TextComponentString; import net.minecraft.util.text.TextComponentTranslation; import net.minecraft.util.text.TextFormatting; +import net.minecraft.world.storage.loot.LootTableManager; import net.minecraftforge.client.settings.KeyConflictContext; import net.minecraftforge.client.settings.KeyModifier; import net.minecraftforge.common.MinecraftForge; @@ -133,6 +137,12 @@ public static void initializeGroovyPreInit() { BracketHandlerManager.init(); VanillaModule.initializeBinding(); ModSupport.init(); + + Loot.TABLE_MANAGER = new LootTableManager(null); + Loot.TABLES.values().forEach(table -> { + ((LootTableAccessor) table).setIsFrozen(false); + ((LootTableAccessor) table).getPools().forEach(pool -> ((LootPoolAccessor) pool).setIsFrozen(false)); + }); boolean wasNull = Loader.instance().activeModContainer() == null; if (wasNull) { diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/loot/GroovyLootCondition.java b/src/main/java/com/cleanroommc/groovyscript/compat/loot/GroovyLootCondition.java new file mode 100644 index 000000000..987ef3ad8 --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/compat/loot/GroovyLootCondition.java @@ -0,0 +1,32 @@ +package com.cleanroommc.groovyscript.compat.loot; + +import com.cleanroommc.groovyscript.api.GroovyLog; +import com.cleanroommc.groovyscript.sandbox.ClosureHelper; +import groovy.lang.Closure; +import net.minecraft.world.storage.loot.LootContext; +import net.minecraft.world.storage.loot.conditions.LootCondition; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.Random; + +public class GroovyLootCondition implements LootCondition { + + public final Closure condition; + + public GroovyLootCondition(Closure condition) { + if (Arrays.equals(condition.getParameterTypes(), new Class[]{Random.class, LootContext.class})) { + this.condition = condition; + } else { + GroovyLog.msg("Error creating custom loot condition: condition must take the following parameters (java.util.Random, net.minecraft.world.storage.loot.LootContext).").error().post(); + this.condition = null; + } + } + + @Override + public boolean testCondition(@NotNull Random rand, @NotNull LootContext context) { + if (this.condition == null) return false; + return ClosureHelper.call(true, condition, rand, context); + } + +} diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/loot/GroovyLootFunction.java b/src/main/java/com/cleanroommc/groovyscript/compat/loot/GroovyLootFunction.java new file mode 100644 index 000000000..e41eea9a9 --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/compat/loot/GroovyLootFunction.java @@ -0,0 +1,39 @@ +package com.cleanroommc.groovyscript.compat.loot; + +import com.cleanroommc.groovyscript.api.GroovyLog; +import com.cleanroommc.groovyscript.sandbox.ClosureHelper; +import groovy.lang.Closure; +import net.minecraft.item.ItemStack; +import net.minecraft.world.storage.loot.LootContext; +import net.minecraft.world.storage.loot.conditions.LootCondition; +import net.minecraft.world.storage.loot.functions.LootFunction; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.Random; + +public class GroovyLootFunction extends LootFunction { + + private final Closure function; + + public GroovyLootFunction(Closure function) { + this(new LootCondition[0], function); + } + + public GroovyLootFunction(LootCondition[] conditions, Closure function) { + super(conditions); + if (Arrays.equals(function.getParameterTypes(), new Class[]{ItemStack.class, Random.class, LootContext.class})) { + this.function = function; + } else { + GroovyLog.msg("Error creating custom loot function: function must take the following parameters (net.minecraft.item.ItemStack, java.util.Random, net.minecraft.world.storage.loot.LootContext).").error().post(); + this.function = null; + } + } + + @Override + public @NotNull ItemStack apply(@NotNull ItemStack stack, @NotNull Random rand, @NotNull LootContext context) { + if (function == null) return ItemStack.EMPTY; + return ClosureHelper.call(ItemStack.EMPTY, function, stack, rand, context); + } + +} diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/loot/Loot.java b/src/main/java/com/cleanroommc/groovyscript/compat/loot/Loot.java new file mode 100644 index 000000000..ca0945246 --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/compat/loot/Loot.java @@ -0,0 +1,214 @@ +package com.cleanroommc.groovyscript.compat.loot; + +import com.cleanroommc.groovyscript.api.GroovyBlacklist; +import com.cleanroommc.groovyscript.api.GroovyLog; +import com.cleanroommc.groovyscript.core.mixin.loot.LootPoolAccessor; +import com.cleanroommc.groovyscript.core.mixin.loot.LootTableAccessor; +import groovy.lang.Closure; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import net.minecraft.client.Minecraft; +import net.minecraft.inventory.EntityEquipmentSlot; +import net.minecraft.util.ResourceLocation; +import net.minecraft.world.WorldServer; +import net.minecraft.world.storage.loot.*; +import net.minecraft.world.storage.loot.conditions.KilledByPlayer; +import net.minecraft.world.storage.loot.conditions.LootCondition; +import net.minecraft.world.storage.loot.conditions.RandomChance; +import net.minecraft.world.storage.loot.conditions.RandomChanceWithLooting; +import org.jetbrains.annotations.ApiStatus; + +import java.util.Arrays; +import java.util.Map; +import java.util.Random; + +public class Loot { + + @GroovyBlacklist + @ApiStatus.Internal + public void onReload() { + TABLES.clear(); + TABLE_MANAGER = new LootTableManager(null); + } + + @GroovyBlacklist + @ApiStatus.Internal + public void afterScriptRun() { + if (Minecraft.getMinecraft().isIntegratedServerRunning() && Minecraft.getMinecraft().getIntegratedServer() != null) { + for (WorldServer world : Minecraft.getMinecraft().getIntegratedServer().worlds) { + world.getLootTableManager().reloadLootTables(); + } + } + } + + public static final Map TABLES = new Object2ObjectOpenHashMap<>(); + public static LootTableManager TABLE_MANAGER; + + public LootTable getTable(String name) { + LootTable lootTable = TABLES.get(new ResourceLocation(name)); + if (lootTable == null) GroovyLog.msg("GroovyScript found 0 LootTable(s) named " + name).post(); + return lootTable; + } + + public void removeTable(String name) { + TABLES.put(new ResourceLocation(name), LootTable.EMPTY_LOOT_TABLE); + } + + public LootPoolBuilder poolBuilder() { + return new LootPoolBuilder(); + } + + public LootPoolBuilder poolBuilder(String name) { + return new LootPoolBuilder(name); + } + + public LootEntryBuilder entryBuilder() { + return new LootEntryBuilder(); + } + + public LootEntryBuilder entryBuilder(String name) { + return new LootEntryBuilder(name); + } + + public void printTables() { + GroovyLog.Msg out = GroovyLog.msg("GroovyScript found the following LootTable(s)"); + TABLES.keySet().forEach(table -> out.add(table.toString())); + if (!out.postIfNotEmpty()) + GroovyLog.msg("GroovyScript found 0 LootTables :thonk:").error().post(); + } + + public void printPools() { + if (TABLES.values().size() == 0) { + GroovyLog.msg("GroovyScript found 0 LootTables :thonk:").error().post(); + return; + } + + GroovyLog.Msg out = GroovyLog.msg("GroovyScript found the following LootPools(s)"); + + TABLES.forEach((rl, table) -> { + if (((LootTableAccessor) table).getPools() == null || ((LootTableAccessor) table).getPools().size() == 0) { + return; + } + out.add(rl.toString()); + ((LootTableAccessor) table).getPools().forEach(pool -> out.add("\t - " + pool.getName())); + }); + + out.postIfNotEmpty(); + } + + public void printPools(String name) { + LootTable table = this.getTable(name); + if (table == null) return; + GroovyLog.Msg out = GroovyLog.msg("GroovyScript found the following LootPools(s)"); + ((LootTableAccessor) table).getPools().forEach(pool -> out.add(pool.getName())); + if (!out.postIfNotEmpty()) + GroovyLog.msg("GroovyScript found 0 LootPools in " + name).error().post(); + } + + public void printEntries() { + if (TABLES.values().size() == 0) { + GroovyLog.msg("GroovyScript found 0 LootTables :thonk:").error().post(); + return; + } + + GroovyLog.Msg out = GroovyLog.msg("GroovyScript found the following LootEntries(s)"); + + TABLES.forEach((rl, table) -> { + if (((LootTableAccessor) table).getPools() == null || ((LootTableAccessor) table).getPools().size() == 0) { + return; + } + out.add(rl.toString()); + ((LootTableAccessor) table).getPools().forEach(pool -> { + out.add("\t - " + pool.getName()); + ((LootPoolAccessor) pool).getLootEntries().forEach(entry -> out.add("\t\t - " + entry.getEntryName())); + }); + }); + + out.postIfNotEmpty(); + } + + public void printEntries(String tableName) { + LootTable table = this.getTable(tableName); + if (table == null) return; + if (((LootTableAccessor) table).getPools() == null || ((LootTableAccessor) table).getPools().size() == 0) { + GroovyLog.msg("GroovyScript found 0 LootPools in " + tableName).error().post(); + return; + } + + GroovyLog.Msg out = GroovyLog.msg("GroovyScript found the following LootEntry(s)"); + ((LootTableAccessor) table).getPools().forEach(pool -> { + out.add(pool.getName()); + ((LootPoolAccessor) pool).getLootEntries().forEach(entry -> out.add("\t" + entry.getEntryName())); + }); + if (!out.postIfNotEmpty()) + GroovyLog.msg("GroovyScript found 0 LootEntries in LootTable " + tableName).error().post(); + } + + public void printEntries(String tableName, String poolName) { + LootTable table = this.getTable(tableName); + if (table == null) return; + LootPool pool = table.getPool(poolName); + if (pool == null) { + GroovyLog.msg("GroovyScript could not find LootPool " + poolName + " in LootTable " + tableName).error().post(); + return; + } + + GroovyLog.Msg out = GroovyLog.msg("GroovyScript found the following LootEntry(s)"); + ((LootPoolAccessor) pool).getLootEntries().forEach(entry -> out.add(entry.getEntryName())); + if (!out.postIfNotEmpty()) + GroovyLog.msg("GroovyScript found 0 LootEntries in LootPool " + poolName).error().post(); + } + + public SetAttributesFunction.Modifier attributeModifier(String attrName, String modifName, int operationIn, float min, float max) { + return this.attributeModifier(attrName, modifName, operationIn, min, max, new EntityEquipmentSlot[0]); + } + + public SetAttributesFunction.Modifier attributeModifier(String attrName, String modifName, int operationIn, float min, float max, EntityEquipmentSlot slotsIn) { + return this.attributeModifier(attrName, modifName, operationIn, min, max, new EntityEquipmentSlot[]{slotsIn}); + } + + public SetAttributesFunction.Modifier attributeModifier(String attrName, String modifName, int operationIn, float min, float max, EntityEquipmentSlot... slotsIn) { + GroovyLog.Msg out = GroovyLog.msg("Error creating Loot Attribute Modifier:"); + out.add(attrName == null || attrName.equals(""), () -> "no attribute type provided"); + out.add(attrName == null || attrName.equals(""), () -> "no modifier type provided"); + out.add(operationIn < 0 || operationIn > 2, () -> "operation number must be between [0,2]"); + if (!out.postIfNotEmpty()) { + return new SetAttributesFunction.Modifier(attrName, modifName, operationIn, new RandomValueRange(min, max), slotsIn); + } + return null; + } + + public static class Conditions { + + public static LootCondition custom(Closure customCondition) { + if (Arrays.equals(customCondition.getParameterTypes(), new Class[]{Random.class, LootContext.class})) { + return new GroovyLootCondition(customCondition); + } + GroovyLog.msg("custom LootConditions require parameters (java.util.Random, net.minecraft.world.storage.loot.LootContext)").error().post(); + return null; + } + + public static LootCondition randomChance(float chance) { + GroovyLog.Msg out = GroovyLog.msg("Error creating LootCondition").error(); + out.add(chance < 0.0f || chance > 1.0f, () -> "randomChance must be in range [0,1]."); + out.postIfNotEmpty(); + return new RandomChance(Math.max(0.0f, Math.min(1.0f, chance))); + } + + public static LootCondition randomChanceWithLooting(float chance, float lootingMultiplier) { + GroovyLog.Msg out = GroovyLog.msg("Error creating LootCondition").error(); + out.add(chance < 0.0f || chance > 1.0f, () -> "randomChance must be in range [0,1]."); + out.add(lootingMultiplier < 0.0f, () -> "lootingMultiplier cannot be less than 0."); + out.postIfNotEmpty(); + return new RandomChanceWithLooting(Math.max(0.0f, Math.min(1.0f, chance)), Math.max(lootingMultiplier, 0.0f)); + } + + public static LootCondition killedByPlayer() { + return new KilledByPlayer(false); + } + + public static LootCondition killedByNonPlayer() { + return new KilledByPlayer(true); + } + } + +} diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/loot/LootEntryBuilder.java b/src/main/java/com/cleanroommc/groovyscript/compat/loot/LootEntryBuilder.java new file mode 100644 index 000000000..aa4402359 --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/compat/loot/LootEntryBuilder.java @@ -0,0 +1,357 @@ +package com.cleanroommc.groovyscript.compat.loot; + +import com.cleanroommc.groovyscript.api.GroovyLog; +import com.google.common.collect.Lists; +import groovy.lang.Closure; +import net.minecraft.enchantment.Enchantment; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.world.storage.loot.LootContext; +import net.minecraft.world.storage.loot.LootEntry; +import net.minecraft.world.storage.loot.LootEntryItem; +import net.minecraft.world.storage.loot.RandomValueRange; +import net.minecraft.world.storage.loot.conditions.KilledByPlayer; +import net.minecraft.world.storage.loot.conditions.LootCondition; +import net.minecraft.world.storage.loot.conditions.RandomChance; +import net.minecraft.world.storage.loot.conditions.RandomChanceWithLooting; +import net.minecraft.world.storage.loot.functions.*; +import org.apache.logging.log4j.Level; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Random; + +public class LootEntryBuilder { + + private static final LootCondition[] EMPTY_CONDITIONS = {}; + + private String name; + private Item item; + private int weight = 1; + private int quality = 0; + private final List functions = new ArrayList<>(); + private final List conditions = new ArrayList<>(); + private final GroovyLog.Msg out = GroovyLog.msg("Error creating GroovyScript LootPool").warn(); + + public LootEntryBuilder() { + this.name = ""; + } + + public LootEntryBuilder(String name) { + this.name = name; + } + + public LootEntryBuilder name(String name) { + this.name = name; + return this; + } + + public LootEntryBuilder item(Item item) { + this.item = item; + return this; + } + + public LootEntryBuilder item(ItemStack stack) { + this.item = stack.getItem(); + if (stack.getMetadata() != 0) { + setMetadata(stack.getMetadata(), stack.getMetadata()); + } + if (stack.hasTagCompound()) { + setNBT(stack.getTagCompound()); + } + return this; + } + + public LootEntryBuilder weight(int weight) { + this.weight = weight; + return this; + } + + public LootEntryBuilder quality(int quality) { + this.quality = quality; + return this; + } + + //===========================LOOT FUNCTIONS=================================== + + public LootEntryBuilder function(LootFunction function) { + this.functions.add(function); + return this; + } + + public LootEntryBuilder function(Closure function) { + if (Arrays.equals(function.getParameterTypes(), new Class[]{ItemStack.class, Random.class, LootContext.class})) { + this.functions.add(new GroovyLootFunction(function)); + } else { + out.add("custom LootFunctions require parameters (net.minecraft.item.ItemStack, java.util.Random, net.minecraft.world.storage.loot.LootContext)"); + } + + return this; + } + + public LootEntryBuilder function(Closure function, LootCondition condition) { + return this.function(function, new LootCondition[]{condition}); + } + + public LootEntryBuilder function(Closure function, LootCondition... conditions) { + if (Arrays.equals(function.getParameterTypes(), new Class[]{ItemStack.class, Random.class, LootContext.class})) { + this.functions.add(new GroovyLootFunction(conditions, function)); + } else { + out.add("custom LootFunctions require parameters (net.minecraft.item.ItemStack, java.util.Random, net.minecraft.world.storage.loot.LootContext)"); + } + + return this; + } + + public LootEntryBuilder enchantWithLevels(boolean isTreasure, int min, int max) { + this.enchantWithLevels(isTreasure, min, max, new LootCondition[0]); + return this; + } + + public LootEntryBuilder enchantWithLevels(boolean isTreasure, int min, int max, LootCondition condition) { + this.enchantWithLevels(isTreasure, min, max, new LootCondition[]{condition}); + return this; + } + + public LootEntryBuilder enchantWithLevels(boolean isTreasure, int min, int max, LootCondition... conditions) { + out.add(min < 0.0f, () -> "enchantWithLevels minimum cannot be less than 0."); + out.add(max < 0.0f, () -> "enchantWithLevels maximum cannot be less than 0."); + this.functions.add(new EnchantWithLevels(conditions, new RandomValueRange(Math.max(min, 0.0f), Math.max(max, 0.0f)), isTreasure)); + return this; + } + + public LootEntryBuilder enchantRandomly() { + return this.enchantRandomly(EMPTY_CONDITIONS); + } + + public LootEntryBuilder enchantRandomly(Enchantment enchantment) { + return this.enchantRandomly(EMPTY_CONDITIONS, enchantment); + } + + public LootEntryBuilder enchantRandomly(Enchantment... enchantments) { + return this.enchantRandomly(EMPTY_CONDITIONS, enchantments); + } + + public LootEntryBuilder enchantRandomly(LootCondition condition, Enchantment enchantment) { + return this.enchantRandomly(new LootCondition[]{condition}, enchantment); + } + + public LootEntryBuilder enchantRandomly(LootCondition condition, Enchantment... enchantments) { + return this.enchantRandomly(new LootCondition[]{condition}, enchantments); + } + + public LootEntryBuilder enchantRandomly(LootCondition[] conditions, Enchantment... enchantments) { + List list = (enchantments != null) ? Lists.newArrayList(enchantments) : null; + if (conditions == null) conditions = EMPTY_CONDITIONS; + this.functions.add(new EnchantRandomly(conditions, list)); + return this; + } + + public LootEntryBuilder lootingEnchantBonus(float min, float max) { + return this.lootingEnchantBonus(min, max, (int) max, EMPTY_CONDITIONS); + } + + public LootEntryBuilder lootingEnchantBonus(float min, float max, int limit) { + return this.lootingEnchantBonus(min, max, limit, EMPTY_CONDITIONS); + } + + public LootEntryBuilder lootingEnchantBonus(float min, float max, LootCondition conditions) { + return this.lootingEnchantBonus(min, max, (int) max, new LootCondition[]{conditions}); + } + + public LootEntryBuilder lootingEnchantBonus(float min, float max, int limit, LootCondition conditions) { + return this.lootingEnchantBonus(min, max, limit, new LootCondition[]{conditions}); + } + + public LootEntryBuilder lootingEnchantBonus(float min, float max, LootCondition... conditions) { + return this.lootingEnchantBonus(min, max, (int) max, conditions); + } + + public LootEntryBuilder lootingEnchantBonus(float min, float max, int limit, LootCondition... conditions) { + out.add(min < 0.0f, () -> "lootingEnchantBonus minimum cannot be less than 0."); + out.add(max < 0.0f, () -> "lootingEnchantBonus maximum cannot be less than 0."); + out.add(limit < 0, () -> "lootingEnchantBonus limit cannot be less than 0."); + if (conditions == null) conditions = EMPTY_CONDITIONS; + this.functions.add(new LootingEnchantBonus(conditions, new RandomValueRange(Math.max(min, 0.0f), Math.max(max, 0.0f)), Math.max(limit, 0))); + return this; + } + + public LootEntryBuilder setDamage(int dmg) { + return this.setDamage(dmg, dmg, EMPTY_CONDITIONS); + } + + public LootEntryBuilder setDamage(int dmg, LootCondition... conditions) { + return this.setDamage(dmg, dmg, conditions); + } + + public LootEntryBuilder setDamage(int min, int max) { + return this.setDamage(min, max, EMPTY_CONDITIONS); + } + + public LootEntryBuilder setDamage(int min, int max, LootCondition conditions) { + return this.setDamage(min, max, new LootCondition[]{conditions}); + } + + public LootEntryBuilder setDamage(int min, int max, LootCondition... conditions) { + out.add(min < 0 || min >= 32767, () -> "setDamage minimum cannot be less than 0 or more than 32766."); + out.add(max < 0 || min >= 32767, () -> "setDamage maximum cannot be less than 0 or more than 32766."); + if (conditions == null) conditions = EMPTY_CONDITIONS; + this.functions.add(new SetDamage(conditions, new RandomValueRange(Math.max(min, 0.0f), Math.max(max, 0.0f)))); + return this; + } + + public LootEntryBuilder setCount(int count) { + return this.setCount(count, count, EMPTY_CONDITIONS); + } + + public LootEntryBuilder setCount(int count, LootCondition... conditions) { + return this.setCount(count, count, conditions); + } + + public LootEntryBuilder setCount(int min, int max) { + return this.setCount(min, max, EMPTY_CONDITIONS); + } + + public LootEntryBuilder setCount(int min, int max, LootCondition conditions) { + return this.setCount(min, max, new LootCondition[]{conditions}); + } + + public LootEntryBuilder setCount(int min, int max, LootCondition... conditions) { + out.add(min < 0, () -> "setCount minimum cannot be less than 0."); + out.add(max < 0, () -> "setCount maximum cannot be less than 0."); + if (conditions == null) conditions = EMPTY_CONDITIONS; + this.functions.add(new SetCount(conditions, new RandomValueRange(Math.max(min, 0.0f), Math.max(max, 0.0f)))); + return this; + } + + public LootEntryBuilder setMetadata(int meta) { + return this.setMetadata(meta, meta, EMPTY_CONDITIONS); + } + + public LootEntryBuilder setMetadata(int meta, LootCondition... conditions) { + return this.setMetadata(meta, meta, conditions); + } + + public LootEntryBuilder setMetadata(int min, int max) { + return this.setMetadata(min, max, EMPTY_CONDITIONS); + } + + public LootEntryBuilder setMetadata(int min, int max, LootCondition conditions) { + return this.setMetadata(min, max, new LootCondition[]{conditions}); + } + + public LootEntryBuilder setMetadata(int min, int max, LootCondition... conditions) { + out.add(min < 0 || min >= 32767, () -> "setMetadata minimum cannot be less than 0 or more than 32766."); + out.add(max < 0 || min >= 32767, () -> "setMetadata maximum cannot be less than 0 or more than 32766."); + if (conditions == null) conditions = EMPTY_CONDITIONS; + this.functions.add(new SetMetadata(conditions, new RandomValueRange(Math.max(min, 0.0f), Math.max(max, 0.0f)))); + return this; + } + + public LootEntryBuilder setNBT(NBTTagCompound tag) { + return this.setNBT(tag, EMPTY_CONDITIONS); + } + + public LootEntryBuilder setNBT(NBTTagCompound tag, LootCondition conditions) { + return this.setNBT(tag, new LootCondition[]{conditions}); + } + + public LootEntryBuilder setNBT(NBTTagCompound tag, LootCondition... conditions) { + if (conditions == null) conditions = EMPTY_CONDITIONS; + this.functions.add(new SetNBT(conditions, tag)); + return this; + } + + public LootEntryBuilder setAttributes(SetAttributesFunction.Modifier modifiers) { + return this.setAttributes(new SetAttributesFunction.Modifier[]{modifiers}, EMPTY_CONDITIONS); + } + + public LootEntryBuilder setAttributes(SetAttributesFunction.Modifier[] modifiers) { + return this.setAttributes(modifiers, EMPTY_CONDITIONS); + } + + public LootEntryBuilder setAttributes(SetAttributesFunction.Modifier modifiers, LootCondition conditions) { + return this.setAttributes(new SetAttributesFunction.Modifier[]{modifiers}, new LootCondition[]{conditions}); + } + + public LootEntryBuilder setAttributes(SetAttributesFunction.Modifier[] modifiers, LootCondition conditions) { + return this.setAttributes(modifiers, new LootCondition[]{conditions}); + } + + public LootEntryBuilder setAttributes(SetAttributesFunction.Modifier[] modifiers, LootCondition... conditions) { + this.functions.add(new SetAttributesFunction(conditions, modifiers)); + return this; + } + + public LootEntryBuilder smelt() { + return this.smelt(EMPTY_CONDITIONS); + } + + public LootEntryBuilder smelt(LootCondition conditions) { + return this.smelt(new LootCondition[]{conditions}); + } + + public LootEntryBuilder smelt(LootCondition... conditions) { + this.functions.add(new Smelt(conditions)); + return this; + } + + //===========================LOOT CONDITIONS=================================== + + public LootEntryBuilder randomChance(float chance) { + out.add(chance < 0.0f || chance > 1.0f, () -> "randomChance must be in range [0,1]."); + this.conditions.add(new RandomChance(Math.max(0.0f, Math.min(1.0f, chance)))); + return this; + } + + public LootEntryBuilder randomChanceWithLooting(float chance, float lootingMultiplier) { + out.add(chance < 0.0f || chance > 1.0f, () -> "randomChance must be in range [0,1]."); + out.add(lootingMultiplier < 0.0f, () -> "lootingMultiplier cannot be less than 0."); + this.conditions.add(new RandomChanceWithLooting(Math.max(0.0f, Math.min(1.0f, chance)), Math.max(lootingMultiplier, 0.0f))); + return this; + } + + public LootEntryBuilder killedByPlayer() { + this.conditions.add(new KilledByPlayer(false)); + return this; + } + + public LootEntryBuilder killedByNonPlayer() { + this.conditions.add(new KilledByPlayer(true)); + return this; + } + + public LootEntryBuilder condition(LootCondition condition) { + this.conditions.add(condition); + return this; + } + + public LootEntryBuilder condition(Closure customCondition) { + if (Arrays.equals(customCondition.getParameterTypes(), new Class[]{Random.class, LootContext.class})) { + this.conditions.add(new GroovyLootCondition(customCondition)); + } else { + out.add("custom LootConditions require parameters (java.util.Random, net.minecraft.world.storage.loot.LootContext)"); + } + + return this; + } + + //===========================BASE=================================== + + private boolean validate() { + if (item == null) out.add("No item provided.").error(); + if (name == null || name.isEmpty()) out.add("No name provided").error(); + if (weight <= 0) out.add("weight <= 0 may make the loot entry unable to be rolled"); + if (quality < 0) out.add("quality < 0 may make the loot entry unable to be rolled"); + out.postIfNotEmpty(); + return out.getLevel() != Level.ERROR; + } + + public LootEntry build() { + if (!validate()) return null; + return new LootEntryItem(item, weight, quality, functions.toArray(new LootFunction[0]), conditions.toArray(new LootCondition[0]), name); + } + +} diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/loot/LootPoolBuilder.java b/src/main/java/com/cleanroommc/groovyscript/compat/loot/LootPoolBuilder.java new file mode 100644 index 000000000..97088fdf7 --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/compat/loot/LootPoolBuilder.java @@ -0,0 +1,116 @@ +package com.cleanroommc.groovyscript.compat.loot; + +import com.cleanroommc.groovyscript.api.GroovyLog; +import groovy.lang.Closure; +import net.minecraft.item.ItemStack; +import net.minecraft.world.storage.loot.LootContext; +import net.minecraft.world.storage.loot.LootEntry; +import net.minecraft.world.storage.loot.LootPool; +import net.minecraft.world.storage.loot.RandomValueRange; +import net.minecraft.world.storage.loot.conditions.KilledByPlayer; +import net.minecraft.world.storage.loot.conditions.LootCondition; +import net.minecraft.world.storage.loot.conditions.RandomChance; +import net.minecraft.world.storage.loot.conditions.RandomChanceWithLooting; +import org.apache.logging.log4j.Level; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Random; + +public class LootPoolBuilder { + + private String name; + private final List lootEntries = new ArrayList<>(); + private final List poolConditions = new ArrayList<>(); + private RandomValueRange rolls; + private RandomValueRange bonusRolls; + private final GroovyLog.Msg out = GroovyLog.msg("Error creating GroovyScript LootPool").warn(); + + public LootPoolBuilder() { + } + + public LootPoolBuilder(String name) { + this.name = name; + } + + public LootPoolBuilder name(String name) { + this.name = name; + return this; + } + + public LootPoolBuilder entry(LootEntry entry) { + this.lootEntries.add(entry); + return this; + } + + public LootPoolBuilder entry(ItemStack stack, int weight) { + this.lootEntries.add(new LootEntryBuilder(stack.getItem().getRegistryName().getNamespace() + ":" + stack.getMetadata()) + .item(stack) + .weight(weight).build()); + return this; + } + + public LootPoolBuilder randomChance(float chance) { + out.add(chance < 0.0f || chance > 1.0f, () -> "randomChance must be in range [0,1]."); + this.poolConditions.add(new RandomChance(chance)); + return this; + } + + public LootPoolBuilder randomChanceWithLooting(float chance, float lootingMultiplier) { + out.add(chance < 0.0f || chance > 1.0f, () -> "randomChance must be in range [0,1]."); + out.add(lootingMultiplier < 0.0f, () -> "lootingMultiplier cannot be less than 0."); + this.poolConditions.add(new RandomChanceWithLooting(chance, lootingMultiplier)); + return this; + } + + public LootPoolBuilder killedByPlayer() { + this.poolConditions.add(new KilledByPlayer(false)); + return this; + } + + public LootPoolBuilder killedByNonPlayer() { + this.poolConditions.add(new KilledByPlayer(true)); + return this; + } + + public LootPoolBuilder condition(LootCondition condition) { + this.poolConditions.add(condition); + return this; + } + + public LootPoolBuilder condition(Closure customCondition) { + if (Arrays.equals(customCondition.getParameterTypes(), new Class[]{Random.class, LootContext.class})) { + this.poolConditions.add(new GroovyLootCondition(customCondition)); + } else { + out.add("custom LootConditions require parameters (java.util.Random, net.minecraft.world.storage.loot.LootContext)"); + } + + return this; + } + + public LootPoolBuilder rollsRange(float min, float max) { + out.add(min < 0.0f, () -> "rollRange minimum cannot be less than 0."); + out.add(max < 0.0f, () -> "rollRange maximum cannot be less than 0."); + this.rolls = new RandomValueRange(Math.max(min, 0.0f), Math.max(max, 0.0f)); + return this; + } + + public LootPoolBuilder bonusRollsRange(float min, float max) { + out.add(min < 0.0f, () -> "rollRange minimum cannot be less than 0."); + out.add(max < 0.0f, () -> "rollRange maximum cannot be less than 0."); + this.bonusRolls = new RandomValueRange(Math.max(min, 0.0f), Math.max(max, 0.0f)); + return this; + } + + private boolean validate() { + if (name == null || name.isEmpty()) out.add("No name provided").error(); + out.postIfNotEmpty(); + return out.getLevel() != Level.ERROR; + } + + public LootPool build() { + return new LootPool(lootEntries.toArray(new LootEntry[0]), poolConditions.toArray(new LootCondition[0]), rolls, bonusRolls, name); + } + +} \ No newline at end of file diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/loot/SetAttributesFunction.java b/src/main/java/com/cleanroommc/groovyscript/compat/loot/SetAttributesFunction.java new file mode 100644 index 000000000..823c351d2 --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/compat/loot/SetAttributesFunction.java @@ -0,0 +1,49 @@ +package com.cleanroommc.groovyscript.compat.loot; + +import net.minecraft.entity.ai.attributes.AttributeModifier; +import net.minecraft.inventory.EntityEquipmentSlot; +import net.minecraft.item.ItemStack; +import net.minecraft.world.storage.loot.LootContext; +import net.minecraft.world.storage.loot.RandomValueRange; +import net.minecraft.world.storage.loot.conditions.LootCondition; +import net.minecraft.world.storage.loot.functions.LootFunction; +import org.jetbrains.annotations.NotNull; + +import java.util.Random; + +public class SetAttributesFunction extends LootFunction { + + private final SetAttributesFunction.Modifier[] modifiers; + + public SetAttributesFunction(LootCondition[] conditionsIn, SetAttributesFunction.Modifier[] modifiersIn) { + super(conditionsIn); + this.modifiers = modifiersIn; + } + + public @NotNull ItemStack apply(@NotNull ItemStack stack, @NotNull Random rand, @NotNull LootContext context) { + for (SetAttributesFunction.Modifier modifier : this.modifiers) { + EntityEquipmentSlot entityequipmentslot = (modifier.slots.length == 0) ? null : modifier.slots[rand.nextInt(modifier.slots.length)]; + stack.addAttributeModifier(modifier.attributeName, new AttributeModifier(modifier.modifierName, modifier.amount.generateFloat(rand), modifier.operation), entityequipmentslot); + } + + return stack; + } + + public static class Modifier { + + public final String modifierName; + public final String attributeName; + public final int operation; + public final RandomValueRange amount; + public final EntityEquipmentSlot[] slots; + + public Modifier(String attrName, String modifName, int operationIn, RandomValueRange randomAmount, EntityEquipmentSlot[] slotsIn) { + this.modifierName = modifName; + this.attributeName = attrName; + this.operation = operationIn; + this.amount = randomAmount; + this.slots = slotsIn; + } + } + +} diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/VanillaModule.java b/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/VanillaModule.java index 5f04ab4a2..1371ccc29 100644 --- a/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/VanillaModule.java +++ b/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/VanillaModule.java @@ -4,11 +4,13 @@ import com.cleanroommc.groovyscript.api.GroovyBlacklist; import com.cleanroommc.groovyscript.api.IScriptReloadable; import com.cleanroommc.groovyscript.compat.content.Content; +import com.cleanroommc.groovyscript.compat.loot.Loot; public class VanillaModule implements IScriptReloadable { public static final Crafting crafting = new Crafting(); public static final Furnace furnace = new Furnace(); + public static final Loot loot = new Loot(); public static final OreDict oreDict = new OreDict(); public static final Player player = new Player(); public static final Content content = new Content(); @@ -17,6 +19,7 @@ public class VanillaModule implements IScriptReloadable { public static void initializeBinding() { GroovyScript.getSandbox().registerBinding("Crafting", crafting); GroovyScript.getSandbox().registerBinding("Furnace", furnace); + GroovyScript.getSandbox().registerBinding("Loot", loot); GroovyScript.getSandbox().registerBinding("OreDict", oreDict); GroovyScript.getSandbox().registerBinding("OreDictionary", oreDict); GroovyScript.getSandbox().registerBinding("Player", player); diff --git a/src/main/java/com/cleanroommc/groovyscript/core/mixin/loot/LootPoolAccessor.java b/src/main/java/com/cleanroommc/groovyscript/core/mixin/loot/LootPoolAccessor.java new file mode 100644 index 000000000..5e66f5271 --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/core/mixin/loot/LootPoolAccessor.java @@ -0,0 +1,19 @@ +package com.cleanroommc.groovyscript.core.mixin.loot; + +import net.minecraft.world.storage.loot.LootEntry; +import net.minecraft.world.storage.loot.LootPool; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.List; + +@Mixin( value = LootPool.class, remap = false ) +public interface LootPoolAccessor { + + @Accessor + public List getLootEntries(); + + @Accessor + void setIsFrozen(boolean val); + +} diff --git a/src/main/java/com/cleanroommc/groovyscript/core/mixin/loot/LootTableAccessor.java b/src/main/java/com/cleanroommc/groovyscript/core/mixin/loot/LootTableAccessor.java new file mode 100644 index 000000000..355e672e0 --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/core/mixin/loot/LootTableAccessor.java @@ -0,0 +1,19 @@ +package com.cleanroommc.groovyscript.core.mixin.loot; + +import net.minecraft.world.storage.loot.LootPool; +import net.minecraft.world.storage.loot.LootTable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.List; + +@Mixin( value = LootTable.class, remap = false ) +public interface LootTableAccessor { + + @Accessor + List getPools(); + + @Accessor("isFrozen") + void setIsFrozen(boolean val); + +} diff --git a/src/main/java/com/cleanroommc/groovyscript/event/EventHandler.java b/src/main/java/com/cleanroommc/groovyscript/event/EventHandler.java index 5724df35b..9c3da605a 100644 --- a/src/main/java/com/cleanroommc/groovyscript/event/EventHandler.java +++ b/src/main/java/com/cleanroommc/groovyscript/event/EventHandler.java @@ -2,6 +2,7 @@ import com.cleanroommc.groovyscript.GroovyScript; import com.cleanroommc.groovyscript.api.GroovyLog; +import com.cleanroommc.groovyscript.compat.loot.Loot; import com.cleanroommc.groovyscript.compat.content.GroovyBlock; import com.cleanroommc.groovyscript.compat.content.GroovyItem; import com.cleanroommc.groovyscript.compat.vanilla.CraftingInfo; @@ -18,7 +19,10 @@ import net.minecraft.inventory.*; import net.minecraft.item.Item; import net.minecraft.item.crafting.IRecipe; +import net.minecraftforge.event.LootTableLoadEvent; import net.minecraft.nbt.NBTTagCompound; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.EventPriority; import net.minecraftforge.client.event.ModelRegistryEvent; import net.minecraftforge.event.RegistryEvent; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; @@ -92,4 +96,13 @@ public static void onItemCrafted(PlayerEvent.ItemCraftedEvent event) { } } + @SubscribeEvent(priority = EventPriority.LOWEST) + public static void onTableLoad(LootTableLoadEvent event) { + if (!Loot.TABLES.containsKey(event.getName())) { + Loot.TABLES.put(event.getName(), event.getTable()); + } else { + Loot.TABLES.get(event.getName()).freeze(); + event.setTable(Loot.TABLES.get(event.getName())); + } + } } diff --git a/src/main/java/com/cleanroommc/groovyscript/registry/ReloadableRegistryManager.java b/src/main/java/com/cleanroommc/groovyscript/registry/ReloadableRegistryManager.java index 8ff1c7a94..1cdfcfcac 100644 --- a/src/main/java/com/cleanroommc/groovyscript/registry/ReloadableRegistryManager.java +++ b/src/main/java/com/cleanroommc/groovyscript/registry/ReloadableRegistryManager.java @@ -117,7 +117,7 @@ public static > Supplier getDummySupplier(Cl } /** - * Reloads JEI completely. Is called after groovy scripts are re ran. + * Reloads JEI completely. Is called after groovy scripts are ran. */ @ApiStatus.Internal @SideOnly(Side.CLIENT) diff --git a/src/main/resources/mixin.groovyscript.json b/src/main/resources/mixin.groovyscript.json index dfd1e8782..403b4f584 100644 --- a/src/main/resources/mixin.groovyscript.json +++ b/src/main/resources/mixin.groovyscript.json @@ -17,7 +17,9 @@ "LoaderControllerMixin", "MetaClassImplMixin", "OreDictionaryAccessor", - "SlotCraftingAccess" + "SlotCraftingAccess", + "loot.LootPoolAccessor", + "loot.LootTableAccessor" ], "client": [ "DefaultResourcePackAccessor",