diff --git a/patches/minecraft/net/minecraft/entity/LivingEntity.java.patch b/patches/minecraft/net/minecraft/entity/LivingEntity.java.patch index 480a31de43..ce8c35c4bd 100644 --- a/patches/minecraft/net/minecraft/entity/LivingEntity.java.patch +++ b/patches/minecraft/net/minecraft/entity/LivingEntity.java.patch @@ -194,7 +194,17 @@ } protected void func_213337_cE() { -@@ -1157,6 +1182,9 @@ +@@ -1144,7 +1169,8 @@ + ResourceLocation resourcelocation = this.func_213346_cF(); + LootTable loottable = this.field_70170_p.func_73046_m().func_200249_aQ().func_186521_a(resourcelocation); + LootContext.Builder lootcontext$builder = this.func_213363_a(p_213354_2_, p_213354_1_); +- loottable.func_216120_b(lootcontext$builder.func_216022_a(LootParameterSets.field_216263_d), this::func_199701_a_); ++ LootContext ctx = lootcontext$builder.func_216022_a(LootParameterSets.field_216263_d); ++ net.minecraftforge.common.ForgeHooks.modifyLoot(loottable.func_216113_a(ctx), ctx).forEach(this::func_199701_a_); + } + + protected LootContext.Builder func_213363_a(boolean p_213363_1_, DamageSource p_213363_2_) { +@@ -1157,6 +1183,9 @@ } public void func_70653_a(Entity p_70653_1_, float p_70653_2_, double p_70653_3_, double p_70653_5_) { @@ -204,7 +214,7 @@ if (!(this.field_70146_Z.nextDouble() < this.func_110148_a(SharedMonsterAttributes.field_111266_c).func_111126_e())) { this.field_70160_al = true; Vec3d vec3d = this.func_213322_ci(); -@@ -1192,12 +1220,7 @@ +@@ -1192,12 +1221,7 @@ return false; } else { BlockState blockstate = this.func_213339_cH(); @@ -218,7 +228,7 @@ } } -@@ -1221,6 +1244,9 @@ +@@ -1221,6 +1245,9 @@ } public void func_180430_e(float p_180430_1_, float p_180430_2_) { @@ -228,7 +238,7 @@ super.func_180430_e(p_180430_1_, p_180430_2_); EffectInstance effectinstance = this.func_70660_b(Effects.field_76430_j); float f = effectinstance == null ? 0.0F : (float)(effectinstance.func_76458_c() + 1); -@@ -1233,7 +1259,7 @@ +@@ -1233,7 +1260,7 @@ int l = MathHelper.func_76128_c(this.field_70161_v); BlockState blockstate = this.field_70170_p.func_180495_p(new BlockPos(j, k, l)); if (!blockstate.func_196958_f()) { @@ -237,7 +247,7 @@ this.func_184185_a(soundtype.func_185842_g(), soundtype.func_185843_a() * 0.5F, soundtype.func_185847_b() * 0.75F); } } -@@ -1302,6 +1328,8 @@ +@@ -1302,6 +1329,8 @@ protected void func_70665_d(DamageSource p_70665_1_, float p_70665_2_) { if (!this.func_180431_b(p_70665_1_)) { @@ -246,7 +256,7 @@ p_70665_2_ = this.func_70655_b(p_70665_1_, p_70665_2_); p_70665_2_ = this.func_70672_c(p_70665_1_, p_70665_2_); float f = p_70665_2_; -@@ -1312,10 +1340,11 @@ +@@ -1312,10 +1341,11 @@ ((ServerPlayerEntity)p_70665_1_.func_76346_g()).func_195067_a(Stats.field_212735_F, Math.round(f1 * 10.0F)); } @@ -259,7 +269,7 @@ this.func_110149_m(this.func_110139_bj() - p_70665_2_); } } -@@ -1357,6 +1386,8 @@ +@@ -1357,6 +1387,8 @@ } public void func_184609_a(Hand p_184609_1_) { @@ -268,7 +278,7 @@ if (!this.field_82175_bq || this.field_110158_av >= this.func_82166_i() / 2 || this.field_110158_av < 0) { this.field_110158_av = -1; this.field_82175_bq = true; -@@ -1715,15 +1746,16 @@ +@@ -1715,15 +1747,16 @@ } this.field_70160_al = true; @@ -287,7 +297,7 @@ } protected float func_189749_co() { -@@ -1733,11 +1765,15 @@ +@@ -1733,11 +1766,15 @@ public void func_213352_e(Vec3d p_213352_1_) { if (this.func_70613_aW() || this.func_184186_bw()) { double d0 = 0.08D; @@ -304,7 +314,7 @@ if (!this.func_70090_H() || this instanceof PlayerEntity && ((PlayerEntity)this).field_71075_bZ.field_75100_b) { if (!this.func_180799_ab() || this instanceof PlayerEntity && ((PlayerEntity)this).field_71075_bZ.field_75100_b) { -@@ -1786,7 +1822,7 @@ +@@ -1786,7 +1823,7 @@ } } else { BlockPos blockpos = new BlockPos(this.field_70165_t, this.func_174813_aQ().field_72338_b - 1.0D, this.field_70161_v); @@ -313,7 +323,7 @@ float f7 = this.field_70122_E ? f5 * 0.91F : 0.91F; this.func_213309_a(this.func_213335_r(f5), p_213352_1_); this.func_213317_d(this.func_213362_f(this.func_213322_ci())); -@@ -1848,6 +1884,7 @@ +@@ -1848,6 +1885,7 @@ f = 0.96F; } @@ -321,7 +331,7 @@ this.func_213309_a(f1, p_213352_1_); this.func_213315_a(MoverType.SELF, this.func_213322_ci()); Vec3d vec3d1 = this.func_213322_ci(); -@@ -1923,6 +1960,7 @@ +@@ -1923,6 +1961,7 @@ } public void func_70071_h_() { @@ -329,7 +339,7 @@ super.func_70071_h_(); this.func_184608_ct(); this.func_205014_p(); -@@ -1954,7 +1992,9 @@ +@@ -1954,7 +1993,9 @@ ItemStack itemstack1 = this.func_184582_a(equipmentslottype); if (!ItemStack.func_77989_b(itemstack1, itemstack)) { @@ -339,7 +349,7 @@ if (!itemstack.func_190926_b()) { this.func_110140_aT().func_111148_a(itemstack.func_111283_C(equipmentslottype)); } -@@ -2403,13 +2443,22 @@ +@@ -2403,13 +2444,22 @@ private void func_184608_ct() { if (this.func_184587_cr()) { @@ -364,7 +374,7 @@ this.func_71036_o(); } } else { -@@ -2443,8 +2492,10 @@ +@@ -2443,8 +2493,10 @@ public void func_184598_c(Hand p_184598_1_) { ItemStack itemstack = this.func_184586_b(p_184598_1_); if (!itemstack.func_190926_b() && !this.func_184587_cr()) { @@ -376,7 +386,7 @@ if (!this.field_70170_p.field_72995_K) { this.func_204802_c(1, true); this.func_204802_c(2, p_184598_1_ == Hand.OFF_HAND); -@@ -2504,6 +2555,9 @@ +@@ -2504,6 +2556,9 @@ vec3d1 = vec3d1.func_178789_a(-this.field_70125_A * ((float)Math.PI / 180F)); vec3d1 = vec3d1.func_178785_b(-this.field_70177_z * ((float)Math.PI / 180F)); vec3d1 = vec3d1.func_72441_c(this.field_70165_t, this.field_70163_u + (double)this.func_70047_e(), this.field_70161_v); @@ -386,7 +396,7 @@ this.field_70170_p.func_195594_a(new ItemParticleData(ParticleTypes.field_197591_B, p_195062_1_), vec3d1.field_72450_a, vec3d1.field_72448_b, vec3d1.field_72449_c, vec3d.field_72450_a, vec3d.field_72448_b + 0.05D, vec3d.field_72449_c); } -@@ -2512,7 +2566,9 @@ +@@ -2512,7 +2567,9 @@ protected void func_71036_o() { if (!this.field_184627_bm.func_190926_b() && this.func_184587_cr()) { this.func_184584_a(this.field_184627_bm, 16); @@ -397,7 +407,7 @@ this.func_184602_cy(); } -@@ -2532,7 +2588,11 @@ +@@ -2532,7 +2589,11 @@ public void func_184597_cx() { if (!this.field_184627_bm.func_190926_b()) { @@ -409,7 +419,7 @@ if (this.field_184627_bm.func_222122_m()) { this.func_184608_ct(); } -@@ -2687,16 +2747,16 @@ +@@ -2687,16 +2748,16 @@ private boolean func_213359_p() { return this.func_213374_dv().map((p_213347_1_) -> { @@ -430,7 +440,7 @@ BlockPos blockpos = p_213368_1_.func_177984_a(); return new Vec3d((double)blockpos.func_177958_n() + 0.5D, (double)blockpos.func_177956_o() + 0.1D, (double)blockpos.func_177952_p() + 0.5D); }); -@@ -2711,8 +2771,13 @@ +@@ -2711,8 +2772,13 @@ @Nullable @OnlyIn(Dist.CLIENT) public Direction func_213376_dz() { @@ -445,7 +455,7 @@ } public boolean func_70094_T() { -@@ -2779,4 +2844,58 @@ +@@ -2779,4 +2845,58 @@ public void func_213334_d(Hand p_213334_1_) { this.func_213361_c(p_213334_1_ == Hand.MAIN_HAND ? EquipmentSlotType.MAINHAND : EquipmentSlotType.OFFHAND); } diff --git a/patches/minecraft/net/minecraft/server/MinecraftServer.java.patch b/patches/minecraft/net/minecraft/server/MinecraftServer.java.patch index d674921265..31caefddb4 100644 --- a/patches/minecraft/net/minecraft/server/MinecraftServer.java.patch +++ b/patches/minecraft/net/minecraft/server/MinecraftServer.java.patch @@ -9,7 +9,23 @@ p_213187_0_.setUncaughtExceptionHandler((p_213206_0_, p_213206_1_) -> { field_147145_h.error(p_213206_1_); }); -@@ -327,6 +327,8 @@ +@@ -213,6 +213,7 @@ + private final LootTableManager field_200256_aj = new LootTableManager(); + private final AdvancementManager field_200257_ak = new AdvancementManager(); + private final FunctionManager field_200258_al = new FunctionManager(this); ++ private final net.minecraftforge.common.loot.LootModifierManager lootManager = new net.minecraftforge.common.loot.LootModifierManager(); + private final FrameTimer field_213215_ap = new FrameTimer(); + private boolean field_205745_an; + private boolean field_212205_ao; +@@ -240,6 +241,7 @@ + this.field_195576_ac.func_219534_a(this.field_200256_aj); + this.field_195576_ac.func_219534_a(this.field_200258_al); + this.field_195576_ac.func_219534_a(this.field_200257_ak); ++ field_195576_ac.func_219534_a(lootManager); + this.field_213217_au = Util.func_215072_e(); + this.field_71294_K = p_i50590_10_; + } +@@ -327,6 +329,8 @@ this.func_200245_b(new TranslationTextComponent("menu.loadingLevel")); SaveHandler savehandler = this.func_71254_M().func_197715_a(p_71247_1_, this); this.func_175584_a(this.func_71270_I(), savehandler); @@ -18,7 +34,7 @@ WorldInfo worldinfo = savehandler.func_75757_d(); WorldSettings worldsettings; if (worldinfo == null) { -@@ -347,13 +349,13 @@ +@@ -347,13 +351,13 @@ } this.func_195560_a(savehandler.func_75765_b(), worldinfo); @@ -33,7 +49,7 @@ if (this.func_71242_L()) { p_213194_2_.func_176127_a(field_213219_c); } -@@ -395,6 +397,7 @@ +@@ -395,6 +399,7 @@ if (dimensiontype != DimensionType.field_223227_a_) { this.field_71305_c.put(dimensiontype, new ServerMultiWorld(serverworld1, this, this.field_213217_au, p_213194_1_, dimensiontype, this.field_71304_b, p_213194_4_)); } @@ -41,7 +57,7 @@ } } -@@ -552,6 +555,7 @@ +@@ -552,6 +557,7 @@ for(ServerWorld serverworld1 : this.func_212370_w()) { if (serverworld1 != null) { try { @@ -49,7 +65,7 @@ serverworld1.close(); } catch (IOException ioexception) { field_147145_h.error("Exception closing the level", (Throwable)ioexception); -@@ -592,6 +596,7 @@ +@@ -592,6 +598,7 @@ public void run() { try { if (this.func_71197_b()) { @@ -57,7 +73,7 @@ this.field_211151_aa = Util.func_211177_b(); this.field_147147_p.func_151315_a(new StringTextComponent(this.field_71286_C)); this.field_147147_p.func_151321_a(new ServerStatusResponse.Version(SharedConstants.func_215069_a().getName(), SharedConstants.func_215069_a().getProtocolVersion())); -@@ -623,9 +628,15 @@ +@@ -623,9 +630,15 @@ this.field_71304_b.func_219897_b(); this.field_71296_Q = true; } @@ -73,7 +89,7 @@ } catch (Throwable throwable1) { field_147145_h.error("Encountered an unexpected exception", throwable1); CrashReport crashreport; -@@ -642,6 +653,7 @@ +@@ -642,6 +655,7 @@ field_147145_h.error("We were unable to save this crash report to disk."); } @@ -81,7 +97,7 @@ this.func_71228_a(crashreport); } finally { try { -@@ -650,6 +662,7 @@ +@@ -650,6 +664,7 @@ } catch (Throwable throwable) { field_147145_h.error("Exception stopping the server", throwable); } finally { @@ -89,7 +105,7 @@ this.func_71240_o(); } -@@ -746,6 +759,7 @@ +@@ -746,6 +761,7 @@ protected void func_71217_p(BooleanSupplier p_71217_1_) { long i = Util.func_211178_c(); @@ -97,7 +113,7 @@ ++this.field_71315_w; this.func_71190_q(p_71217_1_); if (i - this.field_147142_T >= 5000000000L) { -@@ -760,6 +774,7 @@ +@@ -760,6 +776,7 @@ Collections.shuffle(Arrays.asList(agameprofile)); this.field_147147_p.func_151318_b().func_151330_a(agameprofile); @@ -105,7 +121,7 @@ } if (this.field_71315_w % 6000 == 0) { -@@ -787,6 +802,7 @@ +@@ -787,6 +804,7 @@ long i1 = Util.func_211178_c(); this.field_213215_ap.func_181747_a(i1 - i); this.field_71304_b.func_76319_b(); @@ -113,7 +129,7 @@ } protected void func_71190_q(BooleanSupplier p_71190_1_) { -@@ -794,7 +810,8 @@ +@@ -794,7 +812,8 @@ this.func_193030_aL().func_73660_a(); this.field_71304_b.func_219895_b("levels"); @@ -123,7 +139,7 @@ if (serverworld.field_73011_w.func_186058_p() == DimensionType.field_223227_a_ || this.func_71255_r()) { this.field_71304_b.func_194340_a(() -> { return serverworld.func_72912_H().func_76065_j() + " " + Registry.field_212622_k.func_177774_c(serverworld.field_73011_w.func_186058_p()); -@@ -806,6 +823,7 @@ +@@ -806,6 +825,7 @@ } this.field_71304_b.func_76320_a("tick"); @@ -131,7 +147,7 @@ try { serverworld.func_72835_b(p_71190_1_); -@@ -814,12 +832,16 @@ +@@ -814,12 +834,16 @@ serverworld.func_72914_a(crashreport); throw new ReportedException(crashreport); } @@ -148,7 +164,7 @@ this.field_71304_b.func_219895_b("connection"); this.func_147137_ag().func_151269_c(); this.field_71304_b.func_219895_b("players"); -@@ -856,6 +878,7 @@ +@@ -856,6 +880,7 @@ OptionSpec optionspec10 = optionparser.accepts("port").withRequiredArg().ofType(Integer.class).defaultsTo(-1); OptionSpec optionspec11 = optionparser.accepts("serverId").withRequiredArg(); OptionSpec optionspec12 = optionparser.nonOptions(); @@ -156,7 +172,7 @@ try { OptionSet optionset = optionparser.parse(p_main_0_); -@@ -887,6 +910,10 @@ +@@ -887,6 +912,10 @@ GameProfileRepository gameprofilerepository = yggdrasilauthenticationservice.createProfileRepository(); PlayerProfileCache playerprofilecache = new PlayerProfileCache(gameprofilerepository, new File(s, field_152367_a.getName())); String s1 = Optional.ofNullable(optionset.valueOf(optionspec9)).orElse(serverpropertiesprovider.func_219034_a().field_219021_o); @@ -167,7 +183,7 @@ final DedicatedServer dedicatedserver = new DedicatedServer(new File(s), serverpropertiesprovider, DataFixesManager.func_210901_a(), yggdrasilauthenticationservice, minecraftsessionservice, gameprofilerepository, playerprofilecache, LoggingChunkStatusListener::new, s1); dedicatedserver.func_71224_l(optionset.valueOf(optionspec7)); dedicatedserver.func_71208_b(optionset.valueOf(optionspec10)); -@@ -904,6 +931,7 @@ +@@ -904,6 +933,7 @@ Thread thread = new Thread("Server Shutdown Thread") { public void run() { dedicatedserver.func_71263_m(true); @@ -175,7 +191,7 @@ } }; thread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(field_147145_h)); -@@ -948,7 +976,7 @@ +@@ -948,7 +978,7 @@ } public ServerWorld func_71218_a(DimensionType p_71218_1_) { @@ -184,7 +200,7 @@ } public Iterable func_212370_w() { -@@ -987,7 +1015,7 @@ +@@ -987,7 +1017,7 @@ } public String getServerModName() { @@ -193,7 +209,18 @@ } public CrashReport func_71230_b(CrashReport p_71230_1_) { -@@ -1529,6 +1557,31 @@ +@@ -1371,6 +1401,10 @@ + public FunctionManager func_193030_aL() { + return this.field_200258_al; + } ++ ++ public net.minecraftforge.common.loot.LootModifierManager getLootModifierManager() { ++ return lootManager; ++ } + + public void func_193031_aM() { + if (!this.func_213162_bc()) { +@@ -1529,6 +1563,31 @@ public abstract boolean func_213199_b(GameProfile p_213199_1_); diff --git a/patches/minecraft/net/minecraft/world/storage/loot/LootContext.java.patch b/patches/minecraft/net/minecraft/world/storage/loot/LootContext.java.patch index 34c13b68fc..613373652f 100644 --- a/patches/minecraft/net/minecraft/world/storage/loot/LootContext.java.patch +++ b/patches/minecraft/net/minecraft/world/storage/loot/LootContext.java.patch @@ -11,3 +11,18 @@ public static class Builder { private final ServerWorld field_186474_a; private final Map, Object> field_216025_b = Maps.newIdentityHashMap(); +@@ -87,6 +91,14 @@ + this.field_186474_a = p_i46993_1_; + } + ++ public Builder(LootContext context) { ++ this.field_186474_a = context.field_186499_b; ++ this.field_216025_b.putAll(context.field_216036_f); ++ 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; ++ } ++ + public LootContext.Builder func_216023_a(Random p_216023_1_) { + this.field_216027_d = p_216023_1_; + return this; diff --git a/patches/minecraft/net/minecraft/world/storage/loot/LootTable.java.patch b/patches/minecraft/net/minecraft/world/storage/loot/LootTable.java.patch index 3a6dda3ebc..c45b92b05e 100644 --- a/patches/minecraft/net/minecraft/world/storage/loot/LootTable.java.patch +++ b/patches/minecraft/net/minecraft/world/storage/loot/LootTable.java.patch @@ -16,7 +16,23 @@ this.field_216128_f = p_i51265_3_; this.field_216129_g = LootFunctionManager.func_216241_a(p_i51265_3_); } -@@ -92,8 +92,8 @@ +@@ -77,6 +77,7 @@ + + } + ++ @Deprecated //Use other method or manually call ForgeHooks.modifyLoot + public void func_216120_b(LootContext p_216120_1_, Consumer p_216120_2_) { + this.func_216114_a(p_216120_1_, func_216124_a(p_216120_2_)); + } +@@ -84,6 +85,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_); + return list; + } + +@@ -92,8 +94,8 @@ } public void func_216117_a(ValidationResults p_216117_1_, Function p_216117_2_, Set p_216117_3_, LootParameterSet p_216117_4_) { @@ -27,7 +43,7 @@ } for(int j = 0; j < this.field_216128_f.length; ++j) { -@@ -204,6 +204,41 @@ +@@ -204,6 +206,41 @@ } } @@ -69,7 +85,7 @@ public static class Serializer implements JsonDeserializer, JsonSerializer { public LootTable deserialize(JsonElement p_deserialize_1_, Type p_deserialize_2_, JsonDeserializationContext p_deserialize_3_) throws JsonParseException { JsonObject jsonobject = JSONUtils.func_151210_l(p_deserialize_1_, "loot table"); -@@ -229,7 +264,7 @@ +@@ -229,7 +266,7 @@ } } diff --git a/src/main/java/net/minecraftforge/common/ForgeHooks.java b/src/main/java/net/minecraftforge/common/ForgeHooks.java index e09b3903bb..ace5c6d581 100644 --- a/src/main/java/net/minecraftforge/common/ForgeHooks.java +++ b/src/main/java/net/minecraftforge/common/ForgeHooks.java @@ -113,8 +113,11 @@ import net.minecraft.world.IWorld; import net.minecraft.world.IWorldReader; import net.minecraft.world.World; import net.minecraft.world.dimension.DimensionType; +import net.minecraft.world.storage.loot.LootContext; import net.minecraft.world.storage.loot.LootTable; import net.minecraft.world.storage.loot.LootTableManager; +import net.minecraftforge.common.loot.IGlobalLootModifier; +import net.minecraftforge.common.loot.LootModifierManager; import net.minecraftforge.common.util.BlockSnapshot; import net.minecraftforge.event.AnvilUpdateEvent; import net.minecraftforge.event.DifficultyChangeEvent; @@ -1208,4 +1211,19 @@ public class ForgeHooks FurnaceTileEntity.getBurnTimes().entrySet().forEach(e -> VANILLA_BURNS.put(e.getKey().delegate, e.getValue())); } + /** + * 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 + */ + public static List modifyLoot(List list, LootContext context) { + LootModifierManager man = context.getWorld().getServer().getLootModifierManager(); + for(IGlobalLootModifier mod : man.getAllLootMods()) { + list = mod.apply(list, context); + } + return list; + } + } diff --git a/src/main/java/net/minecraftforge/common/loot/GlobalLootModifierSerializer.java b/src/main/java/net/minecraftforge/common/loot/GlobalLootModifierSerializer.java new file mode 100644 index 0000000000..aa198ff501 --- /dev/null +++ b/src/main/java/net/minecraftforge/common/loot/GlobalLootModifierSerializer.java @@ -0,0 +1,80 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2019. + * + * 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.JsonObject; + +import net.minecraft.util.ResourceLocation; +import net.minecraft.world.storage.loot.conditions.ILootCondition; +import net.minecraftforge.registries.GameData; +import net.minecraftforge.registries.IForgeRegistryEntry; + +/** + * Abstract base deserializer for LootModifiers. Takes care of Forge registry things.
+ * Modders should extend this class to return their modifier and implement the abstract + * read method to deserialize from json. + * @param the Type to deserialize + */ +public abstract class GlobalLootModifierSerializer implements IForgeRegistryEntry> { + private ResourceLocation registryName = null; + + public final GlobalLootModifierSerializer setRegistryName(String name) { + if (getRegistryName() != null) + throw new IllegalStateException("Attempted to set registry name with existing registry name! New: " + name + " Old: " + getRegistryName()); + + this.registryName = GameData.checkPrefix(name, true); + return this; + } + + //Helpers + @Override + public final GlobalLootModifierSerializer setRegistryName(ResourceLocation name){ return setRegistryName(name.toString()); } + + public final GlobalLootModifierSerializer setRegistryName(String modID, String name){ return setRegistryName(modID + ":" + name); } + + @Override + public final ResourceLocation getRegistryName() { + return registryName; + } + + /** + * Most mods will likely not need more than
+ * return new MyModifier(conditionsIn)
+ * but any additional properties that are needed will need to be deserialized here. + * @param name The resource location (if needed) + * @param json The full json object (including ILootConditions) + * @param conditionsIn An already deserialized list of ILootConditions + */ + public abstract T read(ResourceLocation location, JsonObject object, ILootCondition[] ailootcondition); + + /** + * Used by Forge's registry system. + */ + @Override + public final Class> getRegistryType() { + return castClass(GlobalLootModifierSerializer.class); + } + + @SuppressWarnings("unchecked") // Need this wrapper, because generics + private static Class castClass(Class cls) + { + return (Class)cls; + } +} diff --git a/src/main/java/net/minecraftforge/common/loot/IGlobalLootModifier.java b/src/main/java/net/minecraftforge/common/loot/IGlobalLootModifier.java new file mode 100644 index 0000000000..17934ce071 --- /dev/null +++ b/src/main/java/net/minecraftforge/common/loot/IGlobalLootModifier.java @@ -0,0 +1,44 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2019. + * + * 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 java.util.List; + +import javax.annotation.Nonnull; + +import net.minecraft.item.ItemStack; +import net.minecraft.world.storage.loot.LootContext; + +/** + * Implementation that defines what a global loot modifier must implement in order to be functional. + * {@link LootModifier} Supplies base functionality; most modders should only need to extend that.
+ * Requires an {@link GlobalLootModifierSerializer} to be registered via json (see forge:loot_modifiers/global_loot_modifiers). + */ +public interface IGlobalLootModifier { + /** + * Applies the modifier to the list of generated loot. This function needs to be responsible for + * checking ILootConditions as well. + * @param generatedLoot the list of ItemStacks that will be dropped, generated by loot tables + * @param context the LootContext, identical to what is passed to loot tables + * @return modified loot drops + */ + @Nonnull + List apply(List generatedLoot, LootContext context); +} diff --git a/src/main/java/net/minecraftforge/common/loot/LootModifier.java b/src/main/java/net/minecraftforge/common/loot/LootModifier.java new file mode 100644 index 0000000000..51d7af5a38 --- /dev/null +++ b/src/main/java/net/minecraftforge/common/loot/LootModifier.java @@ -0,0 +1,65 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2019. + * + * 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 java.util.List; +import java.util.function.Predicate; + +import javax.annotation.Nonnull; + +import net.minecraft.item.ItemStack; +import net.minecraft.world.storage.loot.LootContext; +import net.minecraft.world.storage.loot.conditions.ILootCondition; +import net.minecraft.world.storage.loot.conditions.LootConditionManager; + +/** + * A base implementation of a Global Loot Modifier for modders to extend. + * Takes care of ILootCondition matching and comes with a base serializer + * implementation that takes care of Forge registry things. + */ +public abstract class LootModifier implements IGlobalLootModifier { + protected final ILootCondition[] conditions; + private final Predicate combinedConditions; + + /** + * Constructs a LootModifier. + * @param conditionsIn the ILootConditions that need to be matched before the loot is modified. + */ + protected LootModifier(ILootCondition[] conditionsIn) { + this.conditions = conditionsIn; + this.combinedConditions = LootConditionManager.and(conditionsIn); + } + + @Nonnull + @Override + public final List apply(List generatedLoot, LootContext context) { + return this.combinedConditions.test(context) ? this.doApply(generatedLoot, context) : generatedLoot; + } + + /** + * Applies the modifier to the generated loot (all loot conditions have already been checked + * and have returned true). + * @param generatedLoot the list of ItemStacks that will be dropped, generated by loot tables + * @param context the LootContext, identical to what is passed to loot tables + * @return modified loot drops + */ + @Nonnull + protected abstract List doApply(List generatedLoot, LootContext context); +} diff --git a/src/main/java/net/minecraftforge/common/loot/LootModifierManager.java b/src/main/java/net/minecraftforge/common/loot/LootModifierManager.java new file mode 100644 index 0000000000..00d84dd53e --- /dev/null +++ b/src/main/java/net/minecraftforge/common/loot/LootModifierManager.java @@ -0,0 +1,148 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2019. + * + * 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 java.io.BufferedReader; +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; + +import org.apache.commons.io.IOUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMap.Builder; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import net.minecraft.client.resources.JsonReloadListener; +import net.minecraft.profiler.IProfiler; +import net.minecraft.resources.IResource; +import net.minecraft.resources.IResourceManager; +import net.minecraft.util.JSONUtils; +import net.minecraft.util.ResourceLocation; +import net.minecraft.world.storage.loot.conditions.ILootCondition; +import net.minecraft.world.storage.loot.conditions.LootConditionManager; +import net.minecraft.world.storage.loot.functions.ILootFunction; +import net.minecraft.world.storage.loot.functions.LootFunctionManager; +import net.minecraftforge.registries.ForgeRegistries; + +public class LootModifierManager extends JsonReloadListener { + public static final Logger LOGGER = LogManager.getLogger(); + private static final Gson GSON_INSTANCE = (new GsonBuilder()).registerTypeHierarchyAdapter(ILootFunction.class, new LootFunctionManager.Serializer()).registerTypeHierarchyAdapter(ILootCondition.class, new LootConditionManager.Serializer()).create(); + + private Map registeredLootModifiers = ImmutableMap.of(); + private static final String folder = "loot_modifiers"; + + public LootModifierManager() { + super(GSON_INSTANCE, folder); + } + + @Override + protected void apply(Map resourceList, IResourceManager resourceManagerIn, IProfiler profilerIn) { + Builder builder = ImmutableMap.builder(); + //old way (for reference) + /*Map toLocation = new HashMap(); + resourceList.forEach((location, object) -> { + try { + IGlobalLootModifier modifier = deserializeModifier(location, object); + builder.put(location, modifier); + toLocation.put(modifier, location); + } catch (Exception exception) { + LOGGER.error("Couldn't parse loot modifier {}", location, exception); + } + }); + builder.orderEntriesByValue((x,y) -> { + return toLocation.get(x).compareTo(toLocation.get(y)); + });*/ + //new way + ArrayList finalLocations = new ArrayList(); + ResourceLocation resourcelocation = new ResourceLocation("forge","loot_modifiers/global_loot_modifiers.json"); + try { + //read in all data files from forge:loot_modifiers/global_loot_modifiers in order to do layering + for(IResource iresource : resourceManagerIn.getAllResources(resourcelocation)) { + try ( InputStream inputstream = iresource.getInputStream(); + Reader reader = new BufferedReader(new InputStreamReader(inputstream, StandardCharsets.UTF_8)); + ) { + JsonObject jsonobject = JSONUtils.fromJson(GSON_INSTANCE, reader, JsonObject.class); + boolean replace = jsonobject.get("replace").getAsBoolean(); + if(replace) finalLocations.clear(); + JsonArray entryList = jsonobject.get("entries").getAsJsonArray(); + for(JsonElement entry : entryList) { + String loc = entry.getAsString(); + ResourceLocation res = new ResourceLocation(loc); + if(finalLocations.contains(res)) finalLocations.remove(res); + finalLocations.add(res); + } + } + + catch (RuntimeException | IOException ioexception) { + LOGGER.error("Couldn't read global loot modifier list {} in data pack {}", resourcelocation, iresource.getPackName(), ioexception); + } finally { + IOUtils.closeQuietly((Closeable)iresource); + } + } + } catch (IOException ioexception1) { + LOGGER.error("Couldn't read global loot modifier list from {}", resourcelocation, ioexception1); + } + //use layered config to fetch modifier data files (modifiers missing from config are disabled) + finalLocations.forEach(location -> { + try { + IGlobalLootModifier modifier = deserializeModifier(location, resourceList.get(location)); + builder.put(location, modifier); + } catch (Exception exception) { + LOGGER.error("Couldn't parse loot modifier {}", location, exception); + } + }); + ImmutableMap immutablemap = builder.build(); + this.registeredLootModifiers = immutablemap; + } + + private IGlobalLootModifier deserializeModifier(ResourceLocation location, JsonObject object) { + ILootCondition[] lootConditions = GSON_INSTANCE.fromJson(object.get("conditions"), ILootCondition[].class); + + ResourceLocation serializer = new ResourceLocation(JSONUtils.getString(object, "type")); + + return ForgeRegistries.LOOT_MODIFIER_SERIALIZERS.getValue(serializer).read(location, object, lootConditions); + } + + public static GlobalLootModifierSerializer getSerializerForName(ResourceLocation resourcelocation) { + return ForgeRegistries.LOOT_MODIFIER_SERIALIZERS.getValue(resourcelocation); + } + + /** + * An immutable collection of the registered loot modifiers in layered order. + * @return + */ + public Collection getAllLootMods() { + return registeredLootModifiers.values(); + } + +} diff --git a/src/main/java/net/minecraftforge/registries/ForgeRegistries.java b/src/main/java/net/minecraftforge/registries/ForgeRegistries.java index f3c4f79350..de780920bc 100644 --- a/src/main/java/net/minecraftforge/registries/ForgeRegistries.java +++ b/src/main/java/net/minecraftforge/registries/ForgeRegistries.java @@ -49,6 +49,7 @@ import net.minecraft.world.gen.feature.Feature; import net.minecraft.world.gen.placement.Placement; import net.minecraft.world.gen.surfacebuilders.SurfaceBuilder; import net.minecraftforge.common.ModDimension; +import net.minecraftforge.common.loot.GlobalLootModifierSerializer; import net.minecraftforge.fml.common.registry.GameRegistry; /** @@ -97,6 +98,7 @@ public class ForgeRegistries // Custom forge registries public static final IForgeRegistry MOD_DIMENSIONS = RegistryManager.ACTIVE.getRegistry(ModDimension.class); public static final IForgeRegistry DATA_SERIALIZERS = RegistryManager.ACTIVE.getRegistry(DataSerializerEntry.class); + public static final IForgeRegistry> LOOT_MODIFIER_SERIALIZERS = RegistryManager.ACTIVE.getRegistry(GlobalLootModifierSerializer.class); /** * This function is just to make sure static inializers in other classes have run and setup their registries before we query them. diff --git a/src/main/java/net/minecraftforge/registries/GameData.java b/src/main/java/net/minecraftforge/registries/GameData.java index 94aaa01dc7..41c5f8a421 100644 --- a/src/main/java/net/minecraftforge/registries/GameData.java +++ b/src/main/java/net/minecraftforge/registries/GameData.java @@ -63,6 +63,7 @@ import net.minecraft.world.gen.placement.Placement; import net.minecraft.world.gen.surfacebuilders.SurfaceBuilder; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.ModDimension; +import net.minecraftforge.common.loot.GlobalLootModifierSerializer; import net.minecraftforge.event.RegistryEvent; import net.minecraftforge.event.RegistryEvent.MissingMappings; import net.minecraftforge.fml.LifecycleEventProvider; @@ -141,6 +142,7 @@ public class GameData // Custom forge registries public static final ResourceLocation MODDIMENSIONS = new ResourceLocation("forge:moddimensions"); public static final ResourceLocation SERIALIZERS = new ResourceLocation("minecraft:dataserializers"); + public static final ResourceLocation LOOT_MODIFIER_SERIALIZERS = new ResourceLocation("forge:loot_modifier_serializers"); private static final int MAX_VARINT = Integer.MAX_VALUE - 1; //We were told it is their intention to have everything in a reg be unlimited, so assume that until we find cases where it isnt. @@ -207,6 +209,7 @@ public class GameData // Custom forge registries makeRegistry(MODDIMENSIONS, ModDimension.class ).disableSaving().create(); makeRegistry(SERIALIZERS, DataSerializerEntry.class, 256 /*vanilla space*/, MAX_VARINT).disableSaving().disableOverrides().addCallback(SerializerCallbacks.INSTANCE).create(); + makeRegistry(LOOT_MODIFIER_SERIALIZERS, GlobalLootModifierSerializer.class).disableSaving().disableSync().create(); } private static > RegistryBuilder makeRegistry(ResourceLocation name, Class type) diff --git a/src/test/java/net/minecraftforge/debug/gameplay/loot/GlobalLootModifiersTest.java b/src/test/java/net/minecraftforge/debug/gameplay/loot/GlobalLootModifiersTest.java new file mode 100644 index 0000000000..e124b2a968 --- /dev/null +++ b/src/test/java/net/minecraftforge/debug/gameplay/loot/GlobalLootModifiersTest.java @@ -0,0 +1,206 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2019. + * + * 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.debug.gameplay.loot; + +import java.util.ArrayList; +import java.util.List; + +import javax.annotation.Nonnull; + +import com.google.gson.JsonObject; + +import net.minecraft.enchantment.Enchantment; +import net.minecraft.enchantment.Enchantment.Rarity; +import net.minecraft.enchantment.EnchantmentHelper; +import net.minecraft.enchantment.EnchantmentType; +import net.minecraft.enchantment.Enchantments; +import net.minecraft.inventory.EquipmentSlotType; +import net.minecraft.inventory.Inventory; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.crafting.FurnaceRecipe; +import net.minecraft.item.crafting.IRecipeType; +import net.minecraft.util.JSONUtils; +import net.minecraft.util.ResourceLocation; +import net.minecraft.world.storage.loot.LootContext; +import net.minecraft.world.storage.loot.LootParameterSets; +import net.minecraft.world.storage.loot.LootParameters; +import net.minecraft.world.storage.loot.LootTable; +import net.minecraft.world.storage.loot.conditions.ILootCondition; +import net.minecraftforge.common.loot.GlobalLootModifierSerializer; +import net.minecraftforge.common.loot.LootModifier; +import net.minecraftforge.event.RegistryEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.Mod.EventBusSubscriber; +import net.minecraftforge.items.ItemHandlerHelper; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.ObjectHolder; + +@Mod(GlobalLootModifiersTest.MODID) +public class GlobalLootModifiersTest { + public static final String MODID = "global_loot_test"; + public static final boolean ENABLE = true; + @ObjectHolder(value = MODID) + public static final Enchantment smelt = null; + public GlobalLootModifiersTest() { } + + @EventBusSubscriber(modid = MODID, bus = EventBusSubscriber.Bus.MOD) + public static class EventHandlers { + @SubscribeEvent + public static void registerEnchantments(@Nonnull final RegistryEvent.Register event) { + if (ENABLE) { + event.getRegistry().register(new SmelterEnchantment(Rarity.UNCOMMON, EnchantmentType.DIGGER, new EquipmentSlotType[] {EquipmentSlotType.MAINHAND}).setRegistryName(new ResourceLocation(MODID,"smelt"))); + } + } + + @SubscribeEvent + public static void registerModifierSerializers(@Nonnull final RegistryEvent.Register> event) { + if (ENABLE) { + event.getRegistry().register( + new WheatSeedsConverterModifier.Serializer().setRegistryName(new ResourceLocation(MODID,"wheat_harvest")) + ); + event.getRegistry().register(new SmeltingEnchantmentModifier.Serializer().setRegistryName(new ResourceLocation(MODID,"smelting"))); + event.getRegistry().register(new SilkTouchTestModifier.Serializer().setRegistryName(new ResourceLocation(MODID,"silk_touch_bamboo"))); + } + } + } + + private static class SmelterEnchantment extends Enchantment { + protected SmelterEnchantment(Rarity rarityIn, EnchantmentType typeIn, EquipmentSlotType... slots) { + super(rarityIn, typeIn, slots); + } + } + + /** + * The smelting enchantment causes this modifier to be invoked, via the smelting loot_modifier json + * + */ + private static class SmeltingEnchantmentModifier extends LootModifier { + public SmeltingEnchantmentModifier(ILootCondition[] conditionsIn) { + super(conditionsIn); + } + + @Nonnull + @Override + public List doApply(List generatedLoot, LootContext context) { + ArrayList ret = new ArrayList(); + generatedLoot.forEach((stack) -> ret.add(smelt(stack, context))); + return ret; + } + + private static ItemStack smelt(ItemStack stack, LootContext context) { + return context.getWorld().getRecipeManager().getRecipe(IRecipeType.SMELTING, new Inventory(stack), context.getWorld()) + .map(FurnaceRecipe::getRecipeOutput) + .filter(itemStack -> !itemStack.isEmpty()) + .map(itemStack -> ItemHandlerHelper.copyStackWithSize(itemStack, stack.getCount() * itemStack.getCount())) + .orElse(stack); + } + + private static class Serializer extends GlobalLootModifierSerializer { + @Override + public SmeltingEnchantmentModifier read(ResourceLocation name, JsonObject json, ILootCondition[] conditionsIn) { + return new SmeltingEnchantmentModifier(conditionsIn); + } + } + } + + /** + * When harvesting blocks with bamboo, this modifier is invoked, via the silk_touch_bamboo loot_modifier json + * + */ + private static class SilkTouchTestModifier extends LootModifier { + public SilkTouchTestModifier(ILootCondition[] conditionsIn) { + super(conditionsIn); + } + + @Nonnull + @Override + public List doApply(List generatedLoot, LootContext context) { + ItemStack ctxTool = context.get(LootParameters.TOOL); + //return early if silk-touch is already applied (otherwise we'll get stuck in an infinite loop). + if(EnchantmentHelper.getEnchantments(ctxTool).containsKey(Enchantments.SILK_TOUCH)) return generatedLoot; + ItemStack fakeTool = ctxTool.copy(); + fakeTool.addEnchantment(Enchantments.SILK_TOUCH, 1); + LootContext.Builder builder = new LootContext.Builder(context); + builder.withParameter(LootParameters.TOOL, fakeTool); + LootContext ctx = builder.build(LootParameterSets.BLOCK); + LootTable loottable = context.getWorld().getServer().getLootTableManager().getLootTableFromLocation(context.get(LootParameters.BLOCK_STATE).getBlock().getLootTable()); + return loottable.generate(ctx); + } + + private static class Serializer extends GlobalLootModifierSerializer { + @Override + public SilkTouchTestModifier read(ResourceLocation name, JsonObject json, ILootCondition[] conditionsIn) { + return new SilkTouchTestModifier(conditionsIn); + } + } + } + + /** + * When harvesting wheat with shears, this modifier is invoked via the wheat_harvest loot_modifier json
+ * This modifier checks how many seeds were harvested and turns X seeds into Y wheat (3:1) + * + */ + private static class WheatSeedsConverterModifier extends LootModifier { + private final int numSeedsToConvert; + private final Item itemToCheck; + private final Item itemReward; + public WheatSeedsConverterModifier(ILootCondition[] conditionsIn, int numSeeds, Item itemCheck, Item reward) { + super(conditionsIn); + numSeedsToConvert = numSeeds; + itemToCheck = itemCheck; + itemReward = reward; + } + + @Nonnull + @Override + public List doApply(List generatedLoot, LootContext context) { + // + // Additional conditions can be checked, though as much as possible should be parameterized via JSON data. + // It is better to write a new ILootCondition implementation than to do things here. + // + int numSeeds = 0; + for(ItemStack stack : generatedLoot) { + if(stack.getItem() == itemToCheck) + numSeeds+=stack.getCount(); + } + if(numSeeds >= numSeedsToConvert) { + generatedLoot.removeIf(x -> x.getItem() == itemToCheck); + generatedLoot.add(new ItemStack(itemReward, (numSeeds/numSeedsToConvert))); + numSeeds = numSeeds%numSeedsToConvert; + if(numSeeds > 0) + generatedLoot.add(new ItemStack(itemToCheck, numSeeds)); + } + return generatedLoot; + } + + private static class Serializer extends GlobalLootModifierSerializer { + + @Override + public WheatSeedsConverterModifier read(ResourceLocation name, JsonObject object, ILootCondition[] conditionsIn) { + int numSeeds = JSONUtils.getInt(object, "numSeeds"); + Item seed = ForgeRegistries.ITEMS.getValue(new ResourceLocation((JSONUtils.getString(object, "seedItem")))); + Item wheat = ForgeRegistries.ITEMS.getValue(new ResourceLocation(JSONUtils.getString(object, "replacement"))); + return new WheatSeedsConverterModifier(conditionsIn, numSeeds, seed, wheat); + } + } + } +} diff --git a/src/test/resources/META-INF/mods.toml b/src/test/resources/META-INF/mods.toml index edc2beade1..855b615742 100644 --- a/src/test/resources/META-INF/mods.toml +++ b/src/test/resources/META-INF/mods.toml @@ -61,3 +61,5 @@ loaderVersion="[28,)" modId="custom_tnt_test" [[mods]] modId="new_model_loader_test" +[[mods]] + modId="global_loot_test" diff --git a/src/test/resources/data/forge/loot_modifiers/global_loot_modifiers.json b/src/test/resources/data/forge/loot_modifiers/global_loot_modifiers.json new file mode 100644 index 0000000000..84c06361c2 --- /dev/null +++ b/src/test/resources/data/forge/loot_modifiers/global_loot_modifiers.json @@ -0,0 +1,8 @@ +{ + "replace": false, + "entries": [ + "global_loot_test:silk_touch_bamboo", + "global_loot_test:smelting", + "global_loot_test:wheat_harvest" + ] +} \ No newline at end of file diff --git a/src/test/resources/data/global_loot_test/loot_modifiers/silk_touch_bamboo.json b/src/test/resources/data/global_loot_test/loot_modifiers/silk_touch_bamboo.json new file mode 100644 index 0000000000..c21599966b --- /dev/null +++ b/src/test/resources/data/global_loot_test/loot_modifiers/silk_touch_bamboo.json @@ -0,0 +1,11 @@ +{ + "type": "global_loot_test:silk_touch_bamboo", + "conditions": [ + { + "condition": "minecraft:match_tool", + "predicate": { + "item": "minecraft:bamboo" + } + } + ] +} \ No newline at end of file diff --git a/src/test/resources/data/global_loot_test/loot_modifiers/smelting.json b/src/test/resources/data/global_loot_test/loot_modifiers/smelting.json new file mode 100644 index 0000000000..09b17fd901 --- /dev/null +++ b/src/test/resources/data/global_loot_test/loot_modifiers/smelting.json @@ -0,0 +1,18 @@ +{ + "type": "global_loot_test:smelting", + "conditions": [ + { + "condition": "minecraft:match_tool", + "predicate": { + "enchantments": [ + { + "enchantment": "global_loot_test:smelt", + "levels": { + "min": 1 + } + } + ] + } + } + ] +} \ No newline at end of file diff --git a/src/test/resources/data/global_loot_test/loot_modifiers/wheat_harvest.json b/src/test/resources/data/global_loot_test/loot_modifiers/wheat_harvest.json new file mode 100644 index 0000000000..4f2491ed2e --- /dev/null +++ b/src/test/resources/data/global_loot_test/loot_modifiers/wheat_harvest.json @@ -0,0 +1,18 @@ +{ + "type": "global_loot_test:wheat_harvest", + "conditions": [ + { + "condition": "minecraft:match_tool", + "predicate": { + "item": "minecraft:shears" + } + }, + { + "condition": "block_state_property", + "block":"minecraft:wheat" + } + ], + "seedItem": "minecraft:wheat_seeds", + "numSeeds": 3, + "replacement": "minecraft:wheat" +} \ No newline at end of file