Global Loot Functions (#6401)

This commit is contained in:
Draco18s 2020-02-27 00:13:36 -06:00 committed by GitHub
parent 2ce4f5ece5
commit 58e5f33fad
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 643 additions and 44 deletions

View file

@ -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);
}

View file

@ -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<Integer> optionspec10 = optionparser.accepts("port").withRequiredArg().ofType(Integer.class).defaultsTo(-1);
OptionSpec<String> optionspec11 = optionparser.accepts("serverId").withRequiredArg();
OptionSpec<String> 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<ServerWorld> 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_);

View file

@ -11,3 +11,18 @@
public static class Builder {
private final ServerWorld field_186474_a;
private final Map<LootParameter<?>, 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;

View file

@ -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<ItemStack> p_216120_2_) {
this.func_216114_a(p_216120_1_, func_216124_a(p_216120_2_));
}
@@ -82,6 +83,7 @@
public List<ItemStack> func_216113_a(LootContext p_216113_1_) {
List<ItemStack> 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<LootTable>, JsonSerializer<LootTable> {
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 @@
}
}

View file

@ -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": [
]
}

View file

@ -0,0 +1,8 @@
{
"replace": false,
"entries": [
"global_loot_test:silk_touch_bamboo",
"global_loot_test:smelting",
"global_loot_test:wheat_harvest"
]
}

View file

@ -0,0 +1,11 @@
{
"conditions": [
{
"condition": "minecraft:match_tool",
"predicate": {
"item": "minecraft:bamboo"
}
}
],
"function": "global_loot_test:silk_touch_bamboo"
}

View file

@ -0,0 +1,18 @@
{
"conditions": [
{
"condition": "minecraft:match_tool",
"predicate": {
"enchantments": [
{
"enchantment": "global_loot_test:smelt",
"levels": {
"min": 1
}
}
]
}
}
],
"function": "global_loot_test:smelting"
}

View file

@ -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"
}

View file

@ -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<ItemStack> modifyLoot(List<ItemStack> list, LootContext context) {
LootModifierManager man = context.getWorld().getServer().getLootModifierManager();
for(IGlobalLootModifier mod : man.getAllLootMods()) {
list = mod.apply(list, context);
}
return list;
}
}

View file

@ -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.<br/>
* Modders should extend this class to return their modifier and implement the abstract
* <code>read</code> method to deserialize from json.
* @param <T> the Type to deserialize
*/
public abstract class GlobalLootModifierSerializer<T extends IGlobalLootModifier> implements IForgeRegistryEntry<GlobalLootModifierSerializer<?>> {
private ResourceLocation registryName = null;
public final GlobalLootModifierSerializer<T> 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<T> setRegistryName(ResourceLocation name){ return setRegistryName(name.toString()); }
public final GlobalLootModifierSerializer<T> setRegistryName(String modID, String name){ return setRegistryName(modID + ":" + name); }
@Override
public final ResourceLocation getRegistryName() {
return registryName;
}
/**
* Most mods will likely not need more than<br/>
* <code>return new MyModifier(conditionsIn)</code><br/>
* 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<GlobalLootModifierSerializer<?>> getRegistryType() {
return castClass(GlobalLootModifierSerializer.class);
}
@SuppressWarnings("unchecked") // Need this wrapper, because generics
private static <G> Class<G> castClass(Class<?> cls)
{
return (Class<G>)cls;
}
}

View file

@ -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.<br/>
* 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<ItemStack> apply(List<ItemStack> generatedLoot, LootContext context);
}

View file

@ -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<LootContext> 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<ItemStack> apply(List<ItemStack> 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<ItemStack> doApply(List<ItemStack> generatedLoot, LootContext context);
}

View file

@ -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<ResourceLocation, IGlobalLootModifier> registeredLootModifiers = ImmutableMap.of();
private static final String folder = "loot_modifiers";
public LootModifierManager() {
super(GSON_INSTANCE, folder);
}
@Override
protected void apply(Map<ResourceLocation, JsonObject> resourceList, IResourceManager resourceManagerIn, IProfiler profilerIn) {
Builder<ResourceLocation, IGlobalLootModifier> builder = ImmutableMap.builder();
//old way (for reference)
/*Map<IGlobalLootModifier, ResourceLocation> toLocation = new HashMap<IGlobalLootModifier, ResourceLocation>();
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<ResourceLocation> finalLocations = new ArrayList<ResourceLocation>();
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<ResourceLocation, IGlobalLootModifier> 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<IGlobalLootModifier> getAllLootMods() {
return registeredLootModifiers.values();
}
}

View file

@ -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<ModDimension> MOD_DIMENSIONS = RegistryManager.ACTIVE.getRegistry(ModDimension.class);
public static final IForgeRegistry<DataSerializerEntry> DATA_SERIALIZERS = RegistryManager.ACTIVE.getRegistry(DataSerializerEntry.class);
public static final IForgeRegistry<GlobalLootModifierSerializer<?>> 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.

View file

@ -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 <T extends IForgeRegistryEntry<T>> RegistryBuilder<T> makeRegistry(ResourceLocation name, Class<T> type)

View file

@ -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<Enchantment> 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<IGlobalLootModifierSerializer<?>> 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<ItemStack> doApply(List<ItemStack> generatedLoot, LootContext context) {
ArrayList<ItemStack> ret = new ArrayList<ItemStack>();
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<SmeltingEnchantmentModifier> {
@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<ItemStack> doApply(List<ItemStack> 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<SilkTouchTestModifier> {
@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<br/>
* 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<ItemStack> doApply(List<ItemStack> 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<WheatSeedsConverterModifier> {
@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);
}
}
}
}

View file

@ -59,3 +59,5 @@ loaderVersion="[28,)"
modId="trsr_transformer_test"
[[mods]]
modId="nameplate_render_test"
[[mods]]
modId="global_loot_test"