From 58e5f33fad6c39db71b01e7cfdc4aa4a588a68b8 Mon Sep 17 00:00:00 2001 From: Draco18s Date: Thu, 27 Feb 2020 00:13:36 -0600 Subject: [PATCH] Global Loot Functions (#6401) --- .../minecraft/entity/LivingEntity.java.patch | 52 +++-- .../server/MinecraftServer.java.patch | 67 +++++-- .../world/storage/loot/LootContext.java.patch | 15 ++ .../world/storage/loot/LootTable.java.patch | 22 ++- .../loot_modifiers/global_loot_modifiers.json | 6 + .../loot_modifiers/global_loot_modifiers.json | 8 + .../loot_modifiers/silk_touch_bamboo.json | 11 ++ .../loot_modifiers/smelting.json | 18 ++ .../loot_modifiers/wheat_harvest.json | 18 ++ .../net/minecraftforge/common/ForgeHooks.java | 18 ++ .../loot/GlobalLootModifierSerializer.java | 61 ++++++ .../common/loot/IGlobalLootModifier.java | 25 +++ .../common/loot/LootModifier.java | 46 +++++ .../common/loot/LootModifierManager.java | 126 ++++++++++++ .../registries/ForgeRegistries.java | 2 + .../minecraftforge/registries/GameData.java | 3 + .../loot/GlobalLootModifiersTest.java | 187 ++++++++++++++++++ src/test/resources/META-INF/mods.toml | 2 + 18 files changed, 643 insertions(+), 44 deletions(-) create mode 100644 src/generated/resources/data/forge/loot_modifiers/global_loot_modifiers.json create mode 100644 src/generated_test/resources/data/forge/loot_modifiers/global_loot_modifiers.json create mode 100644 src/generated_test/resources/data/global_loot_test/loot_modifiers/silk_touch_bamboo.json create mode 100644 src/generated_test/resources/data/global_loot_test/loot_modifiers/smelting.json create mode 100644 src/generated_test/resources/data/global_loot_test/loot_modifiers/wheat_harvest.json create mode 100644 src/main/java/net/minecraftforge/common/loot/GlobalLootModifierSerializer.java create mode 100644 src/main/java/net/minecraftforge/common/loot/IGlobalLootModifier.java create mode 100644 src/main/java/net/minecraftforge/common/loot/LootModifier.java create mode 100644 src/main/java/net/minecraftforge/common/loot/LootModifierManager.java create mode 100644 src/test/java/net/minecraftforge/debug/gameplay/loot/GlobalLootModifiersTest.java diff --git a/patches/minecraft/net/minecraft/entity/LivingEntity.java.patch b/patches/minecraft/net/minecraft/entity/LivingEntity.java.patch index 19036b9f5..346d933ad 100644 --- a/patches/minecraft/net/minecraft/entity/LivingEntity.java.patch +++ b/patches/minecraft/net/minecraft/entity/LivingEntity.java.patch @@ -205,7 +205,17 @@ } protected void func_213333_a(DamageSource p_213333_1_, int p_213333_2_, boolean p_213333_3_) { -@@ -1161,6 +1186,9 @@ +@@ -1148,7 +1173,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_) { +@@ -1161,6 +1187,9 @@ } public void func_70653_a(Entity p_70653_1_, float p_70653_2_, double p_70653_3_, double p_70653_5_) { @@ -215,7 +225,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(); -@@ -1196,12 +1224,7 @@ +@@ -1196,12 +1225,7 @@ return false; } else { BlockState blockstate = this.func_213339_cH(); @@ -229,7 +239,7 @@ } } -@@ -1225,6 +1248,11 @@ +@@ -1225,6 +1249,11 @@ } public boolean func_225503_b_(float p_225503_1_, float p_225503_2_) { @@ -241,7 +251,7 @@ boolean flag = super.func_225503_b_(p_225503_1_, p_225503_2_); int i = this.func_225508_e_(p_225503_1_, p_225503_2_); if (i > 0) { -@@ -1248,9 +1276,10 @@ +@@ -1248,9 +1277,10 @@ int i = MathHelper.func_76128_c(this.func_226277_ct_()); int j = MathHelper.func_76128_c(this.func_226278_cu_() - (double)0.2F); int k = MathHelper.func_76128_c(this.func_226281_cx_()); @@ -255,7 +265,7 @@ this.func_184185_a(soundtype.func_185842_g(), soundtype.func_185843_a() * 0.5F, soundtype.func_185847_b() * 0.75F); } -@@ -1319,6 +1348,8 @@ +@@ -1319,6 +1349,8 @@ protected void func_70665_d(DamageSource p_70665_1_, float p_70665_2_) { if (!this.func_180431_b(p_70665_1_)) { @@ -264,7 +274,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 f2 = Math.max(p_70665_2_ - this.func_110139_bj(), 0.0F); -@@ -1328,10 +1359,11 @@ +@@ -1328,10 +1360,11 @@ ((ServerPlayerEntity)p_70665_1_.func_76346_g()).func_195067_a(Stats.field_212735_F, Math.round(f * 10.0F)); } @@ -277,7 +287,7 @@ this.func_110149_m(this.func_110139_bj() - f2); } } -@@ -1385,6 +1417,8 @@ +@@ -1385,6 +1418,8 @@ } public void func_226292_a_(Hand p_226292_1_, boolean p_226292_2_) { @@ -286,7 +296,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; -@@ -1771,15 +1805,16 @@ +@@ -1771,15 +1806,16 @@ } this.field_70160_al = true; @@ -305,7 +315,7 @@ } protected float func_189749_co() { -@@ -1789,11 +1824,15 @@ +@@ -1789,11 +1825,15 @@ public void func_213352_e(Vec3d p_213352_1_) { if (this.func_70613_aW() || this.func_184186_bw()) { double d0 = 0.08D; @@ -322,7 +332,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) { -@@ -1842,7 +1881,7 @@ +@@ -1842,7 +1882,7 @@ } } else { BlockPos blockpos = this.func_226270_aj_(); @@ -331,7 +341,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())); -@@ -1904,6 +1943,7 @@ +@@ -1904,6 +1944,7 @@ f = 0.96F; } @@ -339,7 +349,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(); -@@ -1979,6 +2019,7 @@ +@@ -1979,6 +2020,7 @@ } public void func_70071_h_() { @@ -347,7 +357,7 @@ super.func_70071_h_(); this.func_184608_ct(); this.func_205014_p(); -@@ -2022,7 +2063,9 @@ +@@ -2022,7 +2064,9 @@ ItemStack itemstack1 = this.func_184582_a(equipmentslottype); if (!ItemStack.func_77989_b(itemstack1, itemstack)) { @@ -357,7 +367,7 @@ if (!itemstack.func_190926_b()) { this.func_110140_aT().func_111148_a(itemstack.func_111283_C(equipmentslottype)); } -@@ -2474,13 +2517,22 @@ +@@ -2474,13 +2518,22 @@ private void func_184608_ct() { if (this.func_184587_cr()) { @@ -382,7 +392,7 @@ this.func_71036_o(); } } else { -@@ -2522,8 +2574,10 @@ +@@ -2522,8 +2575,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()) { @@ -394,7 +404,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); -@@ -2583,6 +2637,9 @@ +@@ -2583,6 +2638,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.func_226277_ct_(), this.func_226280_cw_(), this.func_226281_cx_()); @@ -404,7 +414,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); } -@@ -2594,7 +2651,9 @@ +@@ -2594,7 +2652,9 @@ } else { if (!this.field_184627_bm.func_190926_b() && this.func_184587_cr()) { this.func_226293_b_(this.field_184627_bm, 16); @@ -415,7 +425,7 @@ this.func_184602_cy(); } -@@ -2615,7 +2674,11 @@ +@@ -2615,7 +2675,11 @@ public void func_184597_cx() { if (!this.field_184627_bm.func_190926_b()) { @@ -427,7 +437,7 @@ if (this.field_184627_bm.func_222122_m()) { this.func_184608_ct(); } -@@ -2772,16 +2835,16 @@ +@@ -2772,16 +2836,16 @@ private boolean func_213359_p() { return this.func_213374_dv().map((p_213347_1_) -> { @@ -448,7 +458,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); }); -@@ -2797,7 +2860,9 @@ +@@ -2797,7 +2861,9 @@ @OnlyIn(Dist.CLIENT) public Direction func_213376_dz() { BlockPos blockpos = this.func_213374_dv().orElse((BlockPos)null); @@ -459,7 +469,7 @@ } public boolean func_70094_T() { -@@ -2866,4 +2931,58 @@ +@@ -2866,4 +2932,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 48f43f3c3..38d13ab46 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_); }); -@@ -336,6 +336,8 @@ +@@ -221,6 +221,7 @@ + private final LootTableManager field_200256_aj = new LootTableManager(this.field_229734_an_); + 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; +@@ -249,6 +250,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_; + } +@@ -336,6 +338,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) { -@@ -357,13 +359,13 @@ +@@ -357,13 +361,13 @@ worldinfo.func_230145_a_(this.getServerModName(), this.func_230045_q_().isPresent()); 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); } -@@ -407,6 +409,7 @@ +@@ -407,6 +411,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 @@ } } -@@ -565,6 +568,7 @@ +@@ -565,6 +570,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); -@@ -605,6 +609,7 @@ +@@ -605,6 +611,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())); -@@ -636,9 +641,15 @@ +@@ -636,9 +643,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; -@@ -655,6 +666,7 @@ +@@ -655,6 +668,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 { -@@ -663,6 +675,7 @@ +@@ -663,6 +677,7 @@ } catch (Throwable throwable) { field_147145_h.error("Exception stopping the server", throwable); } finally { @@ -89,7 +105,7 @@ this.func_71240_o(); } -@@ -764,6 +777,7 @@ +@@ -764,6 +779,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) { -@@ -778,6 +792,7 @@ +@@ -778,6 +794,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) { -@@ -805,6 +820,7 @@ +@@ -805,6 +822,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_) { -@@ -812,7 +828,8 @@ +@@ -812,7 +830,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()); -@@ -824,6 +841,7 @@ +@@ -824,6 +843,7 @@ } this.field_71304_b.func_76320_a("tick"); @@ -131,7 +147,7 @@ try { serverworld.func_72835_b(p_71190_1_); -@@ -832,12 +850,16 @@ +@@ -832,12 +852,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"); -@@ -878,6 +900,7 @@ +@@ -878,6 +902,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_); -@@ -910,6 +933,10 @@ +@@ -910,6 +935,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)); -@@ -927,6 +954,7 @@ +@@ -927,6 +956,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)); -@@ -971,7 +999,7 @@ +@@ -971,7 +1001,7 @@ } public ServerWorld func_71218_a(DimensionType p_71218_1_) { @@ -184,7 +200,7 @@ } public Iterable func_212370_w() { -@@ -1010,7 +1038,7 @@ +@@ -1010,7 +1040,7 @@ } public String getServerModName() { @@ -193,7 +209,18 @@ } public CrashReport func_71230_b(CrashReport p_71230_1_) { -@@ -1567,6 +1595,31 @@ +@@ -1396,6 +1426,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()) { +@@ -1567,6 +1601,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 b7b53bc31..6f7879f1d 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(); +@@ -105,6 +109,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 b4d6cb466..df0372f8c 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_); } -@@ -90,8 +90,8 @@ +@@ -75,6 +75,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_)); + } +@@ -82,6 +83,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; + } + +@@ -90,8 +92,8 @@ } public void func_227506_a_(ValidationTracker p_227506_1_) { @@ -27,7 +43,7 @@ } for(int j = 0; j < this.field_216128_f.length; ++j) { -@@ -202,6 +202,41 @@ +@@ -202,6 +204,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"); -@@ -227,7 +262,7 @@ +@@ -227,7 +264,7 @@ } } diff --git a/src/generated/resources/data/forge/loot_modifiers/global_loot_modifiers.json b/src/generated/resources/data/forge/loot_modifiers/global_loot_modifiers.json new file mode 100644 index 000000000..52e001dc3 --- /dev/null +++ b/src/generated/resources/data/forge/loot_modifiers/global_loot_modifiers.json @@ -0,0 +1,6 @@ +{ + "comment": "Entries will be loaded, parsed, and executed in order, first to last. Duplicate entries will override earlier entries and missing entries will be ignored while replace:true will clear the list first.", + "replace": false, + "entries": [ + ] +} \ No newline at end of file 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 new file mode 100644 index 000000000..84c06361c --- /dev/null +++ b/src/generated_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/generated_test/resources/data/global_loot_test/loot_modifiers/silk_touch_bamboo.json b/src/generated_test/resources/data/global_loot_test/loot_modifiers/silk_touch_bamboo.json new file mode 100644 index 000000000..3a152a234 --- /dev/null +++ b/src/generated_test/resources/data/global_loot_test/loot_modifiers/silk_touch_bamboo.json @@ -0,0 +1,11 @@ +{ + "conditions": [ + { + "condition": "minecraft:match_tool", + "predicate": { + "item": "minecraft:bamboo" + } + } + ], + "function": "global_loot_test:silk_touch_bamboo" +} \ No newline at end of file diff --git a/src/generated_test/resources/data/global_loot_test/loot_modifiers/smelting.json b/src/generated_test/resources/data/global_loot_test/loot_modifiers/smelting.json new file mode 100644 index 000000000..f51dd62f6 --- /dev/null +++ b/src/generated_test/resources/data/global_loot_test/loot_modifiers/smelting.json @@ -0,0 +1,18 @@ +{ + "conditions": [ + { + "condition": "minecraft:match_tool", + "predicate": { + "enchantments": [ + { + "enchantment": "global_loot_test:smelt", + "levels": { + "min": 1 + } + } + ] + } + } + ], + "function": "global_loot_test:smelting" +} \ No newline at end of file diff --git a/src/generated_test/resources/data/global_loot_test/loot_modifiers/wheat_harvest.json b/src/generated_test/resources/data/global_loot_test/loot_modifiers/wheat_harvest.json new file mode 100644 index 000000000..be50a6f9e --- /dev/null +++ b/src/generated_test/resources/data/global_loot_test/loot_modifiers/wheat_harvest.json @@ -0,0 +1,18 @@ +{ + "conditions": [ + { + "condition": "minecraft:match_tool", + "predicate": { + "item": "minecraft:shears" + } + }, + { + "condition": "block_state_property", + "block":"minecraft:wheat" + } + ], + "function": "global_loot_test:wheat_seeds_test", + "seedItem": "minecraft:wheat_seeds", + "numSeeds": 3, + "replacement": "minecraft:wheat" +} \ 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 496cf05bd..f2e3971cd 100644 --- a/src/main/java/net/minecraftforge/common/ForgeHooks.java +++ b/src/main/java/net/minecraftforge/common/ForgeHooks.java @@ -115,8 +115,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; @@ -1210,4 +1213,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 000000000..027a8c4f6 --- /dev/null +++ b/src/main/java/net/minecraftforge/common/loot/GlobalLootModifierSerializer.java @@ -0,0 +1,61 @@ +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 000000000..e4fe9943c --- /dev/null +++ b/src/main/java/net/minecraftforge/common/loot/IGlobalLootModifier.java @@ -0,0 +1,25 @@ +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 000000000..348e9a20b --- /dev/null +++ b/src/main/java/net/minecraftforge/common/loot/LootModifier.java @@ -0,0 +1,46 @@ +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 000000000..3fd8fb848 --- /dev/null +++ b/src/main/java/net/minecraftforge/common/loot/LootModifierManager.java @@ -0,0 +1,126 @@ +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[] ailootcondition = GSON_INSTANCE.fromJson(object.get("conditions"), ILootCondition[].class); + return ForgeRegistries.LOOT_MODIFIER_SERIALIZERS.getValue(location).read(location, object, ailootcondition); + } + + 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 f3c4f7935..de780920b 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 16f93a36b..9f3c525c7 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 000000000..3c5e4e10e --- /dev/null +++ b/src/test/java/net/minecraftforge/debug/gameplay/loot/GlobalLootModifiersTest.java @@ -0,0 +1,187 @@ +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.IGlobalLootModifierSerializer; +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 LootModifier.Serializer { + @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 LootModifier.Serializer { + @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 LootModifier.Serializer { + + @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 1fb013733..206b36dc1 100644 --- a/src/test/resources/META-INF/mods.toml +++ b/src/test/resources/META-INF/mods.toml @@ -59,3 +59,5 @@ loaderVersion="[28,)" modId="trsr_transformer_test" [[mods]] modId="nameplate_render_test" +[[mods]] + modId="global_loot_test" \ No newline at end of file