diff --git a/patches/minecraft/net/minecraft/loot/LootContext.java.patch b/patches/minecraft/net/minecraft/loot/LootContext.java.patch index 285f41148f..e17f90babc 100644 --- a/patches/minecraft/net/minecraft/loot/LootContext.java.patch +++ b/patches/minecraft/net/minecraft/loot/LootContext.java.patch @@ -1,17 +1,38 @@ --- a/net/minecraft/loot/LootContext.java +++ b/net/minecraft/loot/LootContext.java -@@ -94,6 +94,10 @@ +@@ -94,17 +94,47 @@ return this.field_186499_b; } ++ // ============================== FORGE START ============================== + public int getLootingModifier() { + return net.minecraftforge.common.ForgeHooks.getLootingLevel(func_216031_c(LootParameters.field_216281_a), func_216031_c(LootParameters.field_216284_d), func_216031_c(LootParameters.field_216283_c)); + } ++ ++ private ResourceLocation queriedLootTableId; ++ ++ private LootContext(Random rand, float luckIn, ServerWorld worldIn, Function lootTableManagerIn, Function p_i225885_5_, Map, Object> parametersIn, Map conditionsIn, ResourceLocation queriedLootTableId) { ++ this(rand, luckIn, worldIn, lootTableManagerIn, p_i225885_5_, parametersIn, conditionsIn); ++ if (queriedLootTableId != null) this.queriedLootTableId = queriedLootTableId; ++ } ++ ++ public void setQueriedLootTableId(ResourceLocation queriedLootTableId) { ++ if (this.queriedLootTableId == null && queriedLootTableId != null) this.queriedLootTableId = queriedLootTableId; ++ } ++ public ResourceLocation getQueriedLootTableId() { ++ return this.queriedLootTableId == null? net.minecraftforge.common.loot.LootTableIdCondition.UNKNOWN_LOOT_TABLE : this.queriedLootTableId; ++ } ++ // =============================== FORGE END =============================== + public static class Builder { private final ServerWorld field_186474_a; private final Map, Object> field_216025_b = Maps.newIdentityHashMap(); -@@ -105,6 +109,14 @@ + private final Map field_216026_c = Maps.newHashMap(); + private Random field_216027_d; + private float field_186475_b; ++ private ResourceLocation queriedLootTableId; // Forge: correctly pass around loot table ID with copy constructor + + public Builder(ServerWorld p_i46993_1_) { this.field_186474_a = p_i46993_1_; } @@ -21,8 +42,18 @@ + this.field_216026_c.putAll(context.field_216037_g); + this.field_216027_d = context.field_216035_a; + this.field_186475_b = context.field_186498_a; ++ this.queriedLootTableId = context.queriedLootTableId; + } + public LootContext.Builder func_216023_a(Random p_216023_1_) { this.field_216027_d = p_216023_1_; return this; +@@ -190,7 +220,7 @@ + } + + MinecraftServer minecraftserver = this.field_186474_a.func_73046_m(); +- return new LootContext(random, this.field_186475_b, this.field_186474_a, minecraftserver.func_200249_aQ()::func_186521_a, minecraftserver.func_229736_aP_()::func_227517_a_, this.field_216025_b, this.field_216026_c); ++ return new LootContext(random, this.field_186475_b, this.field_186474_a, minecraftserver.func_200249_aQ()::func_186521_a, minecraftserver.func_229736_aP_()::func_227517_a_, this.field_216025_b, this.field_216026_c, this.queriedLootTableId); + } + } + } diff --git a/patches/minecraft/net/minecraft/loot/LootTable.java.patch b/patches/minecraft/net/minecraft/loot/LootTable.java.patch index 01d2df81d7..09cf31bf82 100644 --- a/patches/minecraft/net/minecraft/loot/LootTable.java.patch +++ b/patches/minecraft/net/minecraft/loot/LootTable.java.patch @@ -28,7 +28,7 @@ public List func_216113_a(LootContext p_216113_1_) { List list = Lists.newArrayList(); this.func_216120_b(p_216113_1_, list::add); -+ list = net.minecraftforge.common.ForgeHooks.modifyLoot(list, p_216113_1_); ++ list = net.minecraftforge.common.ForgeHooks.modifyLoot(this.getLootTableId(), list, p_216113_1_); return list; } @@ -43,7 +43,7 @@ } for(int j = 0; j < this.field_216128_f.length; ++j) { -@@ -173,6 +175,41 @@ +@@ -173,6 +175,48 @@ return new LootTable.Builder(); } @@ -59,6 +59,13 @@ + throw new RuntimeException("Attempted to modify LootTable after being finalized!"); + } + ++ private ResourceLocation lootTableId; ++ public void setLootTableId(final ResourceLocation id) { ++ if (this.lootTableId != null) throw new IllegalStateException("Attempted to rename loot table from '" + this.lootTableId + "' to '" + id + "': this is not supported"); ++ this.lootTableId = java.util.Objects.requireNonNull(id); ++ } ++ public ResourceLocation getLootTableId() { return this.lootTableId; } ++ + public LootPool getPool(String name) { + return field_186466_c.stream().filter(e -> name.equals(e.getName())).findFirst().orElse(null); + } @@ -85,7 +92,7 @@ public static class Builder implements ILootFunctionConsumer { private final List field_216041_a = Lists.newArrayList(); private final List field_216042_b = Lists.newArrayList(); -@@ -227,7 +264,7 @@ +@@ -227,7 +271,7 @@ } } diff --git a/src/generated_test/resources/.cache/cache b/src/generated_test/resources/.cache/cache index 2daa3d7702..8343d95d7a 100644 --- a/src/generated_test/resources/.cache/cache +++ b/src/generated_test/resources/.cache/cache @@ -86,6 +86,7 @@ d65c425e740dc833f29d16606a1171825876be0d data/data_gen_test/tags/blocks/thing/on 1f70ed4ddc878bada5e43d3c44a34157806beda8 data/data_gen_test/tags/blocks/thing/three.json 2c648bca262caaa826ddbbde796d35bb2ee5f03a data/data_gen_test/tags/blocks/thing/two.json b0565ce75fc7880be7517e86a218d1ed9d9e346f data/data_gen_test/tags/blocks/things.json -7572ecfdcedc05dfcabf8a4c69c75e007023256f data/forge/loot_modifiers/global_loot_modifiers.json +f4d3a59005dafa334c835e43ed0047487e17324d data/forge/loot_modifiers/global_loot_modifiers.json +5bd8a05038d2633f89b3b1c5ac319eb4b347b335 data/global_loot_test/loot_modifiers/dungeon_loot.json f65e40b06ce7ad18e053ec88058b951f98ae5f40 data/global_loot_test/loot_modifiers/smelting.json f991cfd612acf5ce98a43f4771ee04727219ae53 data/global_loot_test/loot_modifiers/wheat_harvest.json diff --git a/src/generated_test/resources/data/forge/loot_modifiers/global_loot_modifiers.json b/src/generated_test/resources/data/forge/loot_modifiers/global_loot_modifiers.json index 357a58d7ab..d1da71af2c 100644 --- a/src/generated_test/resources/data/forge/loot_modifiers/global_loot_modifiers.json +++ b/src/generated_test/resources/data/forge/loot_modifiers/global_loot_modifiers.json @@ -1,6 +1,7 @@ { "replace": false, "entries": [ + "global_loot_test:dungeon_loot", "global_loot_test:wheat_harvest", "global_loot_test:smelting" ] diff --git a/src/generated_test/resources/data/global_loot_test/loot_modifiers/dungeon_loot.json b/src/generated_test/resources/data/global_loot_test/loot_modifiers/dungeon_loot.json new file mode 100644 index 0000000000..9c9b40faf5 --- /dev/null +++ b/src/generated_test/resources/data/global_loot_test/loot_modifiers/dungeon_loot.json @@ -0,0 +1,10 @@ +{ + "conditions": [ + { + "condition": "forge:loot_table_id", + "loot_table_id": "minecraft:chests/simple_dungeon" + } + ], + "multiplication_factor": 2, + "type": "global_loot_test:dungeon_loot" +} \ No newline at end of file diff --git a/src/main/java/net/minecraftforge/common/ForgeHooks.java b/src/main/java/net/minecraftforge/common/ForgeHooks.java index c4bbf18779..0b01f969ab 100644 --- a/src/main/java/net/minecraftforge/common/ForgeHooks.java +++ b/src/main/java/net/minecraftforge/common/ForgeHooks.java @@ -131,6 +131,7 @@ import net.minecraft.world.biome.BiomeGenerationSettings; import net.minecraft.world.biome.MobSpawnInfo; import net.minecraftforge.common.loot.IGlobalLootModifier; import net.minecraftforge.common.loot.LootModifierManager; +import net.minecraftforge.common.loot.LootTableIdCondition; import net.minecraftforge.common.util.BlockSnapshot; import net.minecraftforge.common.world.BiomeGenerationSettingsBuilder; import net.minecraftforge.common.world.ForgeWorldType; @@ -861,6 +862,7 @@ public class ForgeHooks { que.push(new LootTableContext(name, custom)); ret = gson.fromJson(data, LootTable.class); + ret.setLootTableId(name); que.pop(); } catch (JsonParseException e) @@ -1253,16 +1255,42 @@ public class ForgeHooks /** * All loot table drops should be passed to this function so that mod added effects * (e.g. smelting enchantments) can be processed. + * * @param list The loot generated * @param context The loot context that generated that loot * @return The modified list + * + * @deprecated Use {@link #modifyLoot(ResourceLocation, List, LootContext)} instead. + * + * @implNote This method will use the {@linkplain LootTableIdCondition#UNKNOWN_LOOT_TABLE + * unknown loot table marker} when redirecting. */ + @Deprecated public static List modifyLoot(List list, LootContext context) { + return modifyLoot(LootTableIdCondition.UNKNOWN_LOOT_TABLE, list, context); + } + + /** + * Handles the modification of loot table drops via the registered Global Loot Modifiers, + * so that custom effects can be processed. + * + *

All loot-table generated loot should be passed to this function.

+ * + * @param lootTableId The ID of the loot table currently being queried + * @param generatedLoot The loot generated by the loot table + * @param context The loot context that generated the loot, unmodified + * @return The modified list of drops + * + * @apiNote The given context will be modified by this method to also store the ID of the + * loot table being queried. + */ + public static List modifyLoot(ResourceLocation lootTableId, List generatedLoot, LootContext context) { + context.setQueriedLootTableId(lootTableId); // In case the ID was set via copy constructor, this will be ignored: intended LootModifierManager man = ForgeInternalHandler.getLootModifierManager(); - for(IGlobalLootModifier mod : man.getAllLootMods()) { - list = mod.apply(list, context); + for (IGlobalLootModifier mod : man.getAllLootMods()) { + generatedLoot = mod.apply(generatedLoot, context); } - return list; + return generatedLoot; } public static List getModPacks() diff --git a/src/main/java/net/minecraftforge/common/ForgeMod.java b/src/main/java/net/minecraftforge/common/ForgeMod.java index ed5ba2efdb..852090df72 100644 --- a/src/main/java/net/minecraftforge/common/ForgeMod.java +++ b/src/main/java/net/minecraftforge/common/ForgeMod.java @@ -27,12 +27,15 @@ import net.minecraft.entity.ai.attributes.RangedAttribute; import net.minecraft.fluid.Fluid; import net.minecraft.item.Items; import net.minecraft.util.SoundEvent; +import net.minecraft.util.registry.Registry; import net.minecraft.world.storage.IServerConfiguration; import net.minecraft.world.storage.SaveFormat; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.client.ForgeHooksClient; import net.minecraftforge.common.data.ExistingFileHelper; import net.minecraftforge.common.data.ForgeFluidTagsProvider; +import net.minecraftforge.common.loot.GlobalLootModifierSerializer; +import net.minecraftforge.common.loot.LootTableIdCondition; import net.minecraftforge.eventbus.api.IEventBus; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fluids.FluidAttributes; @@ -48,7 +51,6 @@ import net.minecraftforge.registries.DeferredRegister; import net.minecraftforge.server.command.EnumArgument; import net.minecraftforge.server.command.ModIdArgument; import net.minecraftforge.registries.ForgeRegistries; -import net.minecraftforge.registries.IForgeRegistry; import net.minecraftforge.versions.forge.ForgeVersion; import net.minecraftforge.versions.mcp.MCPVersion; @@ -288,4 +290,12 @@ public class ForgeMod implements WorldPersistenceHooks.WorldPersistenceHook event.getRegistry().register(new ConditionalRecipe.Serializer>().setRegistryName(new ResourceLocation("forge", "conditional"))); } + + @SubscribeEvent //ModBus + @SuppressWarnings("unused") // automatically called by Forge + public void registerLootData(RegistryEvent.Register> event) + { + // Ignore the event itself: this is done only not to statically initialize our custom LootConditionType + Registry.register(Registry.LOOT_CONDITION_TYPE, new ResourceLocation("forge:loot_table_id"), LootTableIdCondition.LOOT_TABLE_ID); + } } diff --git a/src/main/java/net/minecraftforge/common/loot/LootTableIdCondition.java b/src/main/java/net/minecraftforge/common/loot/LootTableIdCondition.java new file mode 100644 index 0000000000..97ecc305f7 --- /dev/null +++ b/src/main/java/net/minecraftforge/common/loot/LootTableIdCondition.java @@ -0,0 +1,93 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2020. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.common.loot; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonObject; +import com.google.gson.JsonSerializationContext; +import net.minecraft.loot.ILootSerializer; +import net.minecraft.loot.LootConditionType; +import net.minecraft.loot.LootContext; +import net.minecraft.loot.conditions.ILootCondition; +import net.minecraft.util.JSONUtils; +import net.minecraft.util.ResourceLocation; + +public class LootTableIdCondition implements ILootCondition +{ + // TODO Forge Registry at some point? + public static final LootConditionType LOOT_TABLE_ID = new LootConditionType(new LootTableIdCondition.Serializer()); + public static final ResourceLocation UNKNOWN_LOOT_TABLE = new ResourceLocation("forge", "unknown_loot_table"); + + private final ResourceLocation targetLootTableId; + + private LootTableIdCondition(final ResourceLocation targetLootTableId) + { + this.targetLootTableId = targetLootTableId; + } + + @Override + public LootConditionType func_230419_b_() + { + return LOOT_TABLE_ID; + } + + @Override + public boolean test(LootContext lootContext) + { + return lootContext.getQueriedLootTableId().equals(this.targetLootTableId); + } + + public static Builder builder(final ResourceLocation targetLootTableId) + { + return new Builder(targetLootTableId); + } + + public static class Builder implements ILootCondition.IBuilder + { + private final ResourceLocation targetLootTableId; + + public Builder(ResourceLocation targetLootTableId) + { + if (targetLootTableId == null) throw new IllegalArgumentException("Target loot table must not be null"); + this.targetLootTableId = targetLootTableId; + } + + @Override + public ILootCondition build() + { + return new LootTableIdCondition(this.targetLootTableId); + } + } + + public static class Serializer implements ILootSerializer + { + @Override + public void serialize(JsonObject object, LootTableIdCondition instance, JsonSerializationContext ctx) + { + object.addProperty("loot_table_id", instance.targetLootTableId.toString()); + } + + @Override + public LootTableIdCondition deserialize(JsonObject object, JsonDeserializationContext ctx) + { + return new LootTableIdCondition(new ResourceLocation(JSONUtils.getString(object, "loot_table_id"))); + } + } +} diff --git a/src/test/java/net/minecraftforge/debug/gameplay/loot/GlobalLootModifiersTest.java b/src/test/java/net/minecraftforge/debug/gameplay/loot/GlobalLootModifiersTest.java index 99441f6638..c8e23fa16a 100644 --- a/src/test/java/net/minecraftforge/debug/gameplay/loot/GlobalLootModifiersTest.java +++ b/src/test/java/net/minecraftforge/debug/gameplay/loot/GlobalLootModifiersTest.java @@ -20,6 +20,7 @@ package net.minecraftforge.debug.gameplay.loot; import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; import net.minecraft.advancements.criterion.EnchantmentPredicate; import net.minecraft.advancements.criterion.ItemPredicate; import net.minecraft.advancements.criterion.MinMaxBounds; @@ -48,7 +49,9 @@ import net.minecraft.util.JSONUtils; import net.minecraft.util.ResourceLocation; import net.minecraftforge.common.data.GlobalLootModifierProvider; import net.minecraftforge.common.loot.GlobalLootModifierSerializer; +import net.minecraftforge.common.loot.IGlobalLootModifier; import net.minecraftforge.common.loot.LootModifier; +import net.minecraftforge.common.loot.LootTableIdCondition; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.RegistryObject; import net.minecraftforge.fml.common.Mod; @@ -62,6 +65,7 @@ import net.minecraftforge.registries.ForgeRegistries; import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; @Mod(GlobalLootModifiersTest.MODID) public class GlobalLootModifiersTest { @@ -80,6 +84,7 @@ public class GlobalLootModifiersTest { private static final DeferredRegister> GLM = DeferredRegister.create(ForgeRegistries.LOOT_MODIFIER_SERIALIZERS, MODID); private static final DeferredRegister ENCHANTS = DeferredRegister.create(ForgeRegistries.ENCHANTMENTS, MODID); + private static final RegistryObject DUNGEON_LOOT = GLM.register("dungeon_loot", DungeonLootEnhancerModifier.Serializer::new); private static final RegistryObject SMELTING = GLM.register("smelting", SmeltingEnchantmentModifier.Serializer::new); private static final RegistryObject WHEATSEEDS = GLM.register("wheat_harvest", WheatSeedsConverterModifier.Serializer::new); private static final RegistryObject SILKTOUCH = GLM.register("silk_touch_bamboo", SilkTouchTestModifier.Serializer::new); @@ -121,6 +126,10 @@ public class GlobalLootModifiersTest { }, 3, Items.WHEAT_SEEDS, Items.WHEAT) ); + + add("dungeon_loot", DUNGEON_LOOT.get(), new DungeonLootEnhancerModifier( + new ILootCondition[] { LootTableIdCondition.builder(new ResourceLocation("chests/simple_dungeon")).build() }) + ); } } @@ -263,4 +272,41 @@ public class GlobalLootModifiersTest { } } } + + private static class DungeonLootEnhancerModifier extends LootModifier { + private final int multiplicationFactor; + + public DungeonLootEnhancerModifier(final ILootCondition[] conditionsIn, final int multiplicationFactor) { + super(conditionsIn); + this.multiplicationFactor = multiplicationFactor; + } + + public DungeonLootEnhancerModifier(final ILootCondition[] conditionsIn) { + this(conditionsIn, 2); + } + + @Override + protected List doApply(List generatedLoot, LootContext context) { + return generatedLoot.stream() + .map(ItemStack::copy) + .peek(stack -> stack.setCount(Math.min(stack.getMaxStackSize(), stack.getCount() * this.multiplicationFactor))) + .collect(Collectors.toList()); + } + + private static class Serializer extends GlobalLootModifierSerializer { + @Override + public DungeonLootEnhancerModifier read(ResourceLocation location, JsonObject object, ILootCondition[] conditions) { + final int multiplicationFactor = JSONUtils.getInt(object, "multiplication_factor", 2); + if (multiplicationFactor <= 0) throw new JsonParseException("Unable to set a multiplication factor to a number lower than 1"); + return new DungeonLootEnhancerModifier(conditions, multiplicationFactor); + } + + @Override + public JsonObject write(DungeonLootEnhancerModifier instance) { + final JsonObject obj = this.makeConditions(instance.conditions); + obj.addProperty("multiplication_factor", instance.multiplicationFactor); + return obj; + } + } + } }