Created
November 15, 2019 23:08
-
-
Save Commoble/a821e8fe62238c913bd189aaffe7e425 to your computer and use it in GitHub Desktop.
Autogenerating data jsons in minecraft forge
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// generating data jsons from code can take advantage of the java compiler to enforce correctness, | |
// as well as potentially taking advantage of helper functions and classes to deal with repetitive bits of json (not demonstrated here) | |
// this is done by subscribing to the GatherDataEvent MOD event | |
// this refers to a few things from this gist: | |
// https://gist.github.com/Commoble/407716790fc1d3717b1a3eaac29f7423 | |
// let's say we want to generate this loot table from code: | |
{ | |
"type": "minecraft:chest", | |
"pools": | |
[ | |
{ | |
"rolls": | |
{ | |
"min": 3, | |
"max": 10, | |
"type": "minecraft:uniform" | |
}, | |
"entries": | |
[ | |
{ | |
"type": "minecraft:loot_table", | |
"name": "dungeonfist:subtables/enchanted_weapons", | |
"weight": 1, | |
"functions": | |
[ | |
{ | |
"function": "dungeonfist:apply_functions_if_item_has_tag", | |
"tag": "dungeonfist:wooden_tools", | |
"functions": | |
[ | |
{ | |
"function": "minecraft:set_name", | |
"name": "Yep, it's wood" | |
} | |
] | |
} | |
] | |
} | |
] | |
} | |
] | |
} | |
// this event only fires when you launch runData from your IDE or wherever | |
// the intended use case is that you generate the data ahead-of-time and ship the mod with the generated data | |
// the launch config generated by the default build.gradle generates them in yourmod/src/generated/resources, | |
// this folder isn't read by minecraft so you'll need to move them manually or just change the launch config | |
// if your file path has any spaces in it, you'll need to put quotes around the path in the args as well, | |
// or it'll ignore everything in the path after the first space and generate them somewhere weird | |
// if your IDE doesn't auto-refresh when files are added externally, make sure to refresh it after you do this | |
@SubscribeEvent | |
public static void onGatherData(GatherDataEvent event) | |
{ | |
// so anything that is needed by the data gatherer must go here, such as registering custom loot functions/conditions | |
// in particular, FMLCommonSetupEvent does not fire | |
// if you registered any loot functions/conditions there, you'll need them here as well | |
// register custom loot table functions/conditions | |
LootFunctionManager.registerFunction(new ApplyFunctionsIfItemHasTag.Serializer()); | |
// add loot table generators | |
event.getGenerator().addProvider(new DungeonLootTableProvider(event.getGenerator())); | |
} | |
//////////////// | |
// that event needs a LootTableProvider, so we'll make one of those | |
//////////////// | |
public class DungeonLootTableProvider extends LootTableProvider | |
{ | |
// if you're familiar with the top-level property "type" in loot tables | |
// e.g. "type": "minecraft:chest" or "type": "minecraft:generic" | |
// this determines what sorts of parameters are available to the loot table for conditions, etc | |
// and when we generate loot tables, we define them with their parameter sets in this list here | |
// technically, loot table builders have a method to set it in the builder | |
// but this seems to be ALWAYS overridden but what we define here | |
private final List<Pair<Supplier<Consumer<BiConsumer<ResourceLocation, LootTable.Builder>>>, LootParameterSet>> loot_tables = | |
ImmutableList.of(Pair.of(DungeonChestLootTables::new, LootParameterSets.CHEST)); | |
public DungeonLootTableProvider(DataGenerator dataGenerator) | |
{ | |
super(dataGenerator); | |
} | |
@Override | |
protected List<Pair<Supplier<Consumer<BiConsumer<ResourceLocation, LootTable.Builder>>>, LootParameterSet>> getTables() | |
{ | |
return this.loot_tables; | |
} | |
@Override | |
public String getName() | |
{ | |
// TODO Auto-generated method stub | |
return DungeonFist.MODID + ":loot_tables"; | |
} | |
@Override | |
protected void validate(Map<ResourceLocation, LootTable> map, ValidationResults validationresults) | |
{ | |
map.forEach((resourceLocation, lootTable) -> { | |
LootTableManager.func_215302_a(validationresults, resourceLocation, lootTable, map::get); | |
}); | |
} | |
} | |
//////////////// | |
// We need one more thing: that DungeonChestLootTables class we referred to above | |
// this is where the actual loot table objects are built | |
// Now, when we autogenerate a loot table that pulls from other loot tables, | |
// we'll need those other loot tables to have been generated first | |
// so we'll create a few of them here | |
//////////////// | |
public class DungeonChestLootTables implements Consumer<BiConsumer<ResourceLocation, LootTable.Builder>> | |
{ | |
public ResourceLocation TEST_CHEST = new ResourceLocation(DungeonFist.MODID, "chests/test"); | |
// subtable locations | |
public ResourceLocation ENCHANTED_WEAPONS = new ResourceLocation(DungeonFist.MODID, "subtables/enchanted_weapons"); | |
public ResourceLocation WEAPONS = new ResourceLocation(DungeonFist.MODID, "subtables/weapons"); | |
public ResourceLocation WOODEN_TOOLS_TAG = new ResourceLocation(DungeonFist.MODID, "wooden_tools"); | |
@Override | |
public void accept(BiConsumer<ResourceLocation, Builder> builderConsumer) | |
{ | |
builderConsumer.accept( | |
this.WEAPONS, | |
LootTable.builder().addLootPool( | |
LootPool.builder().rolls(ConstantRange.of(1)) | |
.addEntry(ItemLootEntry.builder(Items.IRON_SWORD).weight(40)) | |
.addEntry(ItemLootEntry.builder(Items.IRON_AXE).weight(40)) | |
.addEntry(ItemLootEntry.builder(Items.BOW).weight(11)) | |
.addEntry(ItemLootEntry.builder(Items.GOLDEN_SWORD).weight(1)) | |
.addEntry(ItemLootEntry.builder(Items.GOLDEN_AXE).weight(1)) | |
.addEntry(ItemLootEntry.builder(Items.WOODEN_SWORD).weight(40)) | |
.addEntry(ItemLootEntry.builder(Items.WOODEN_AXE).weight(1)) | |
.addEntry(ItemLootEntry.builder(Items.DIAMOND_SWORD).weight(1)) | |
.addEntry(ItemLootEntry.builder(Items.DIAMOND_AXE).weight(1)) | |
.addEntry(ItemLootEntry.builder(Items.TRIDENT).weight(1)) | |
) | |
); | |
builderConsumer.accept( | |
this.ENCHANTED_WEAPONS, | |
LootTable.builder().addLootPool( | |
LootPool.builder().rolls(ConstantRange.of(1)).addEntry( | |
TableLootEntry.builder(this.WEAPONS).weight(1).acceptFunction( | |
// unmapped function names: This gets the builder for EnchantWithLevels, | |
// sets the random level range to between 1 and 5, and enables treasure enchantments | |
EnchantWithLevels.func_215895_a(RandomValueRange.func_215837_a(1F, 5F)).func_216059_e() | |
) | |
).addEntry( | |
TableLootEntry.builder(this.WEAPONS).weight(1).acceptFunction( | |
EnchantWithLevels.func_215895_a(RandomValueRange.func_215837_a(10F, 20F)).func_216059_e() | |
) | |
) | |
) | |
); | |
builderConsumer.accept( | |
this.TEST_CHEST, | |
LootTable.builder().addLootPool( | |
LootPool.builder().rolls(RandomValueRange.func_215837_a(3F, 10F)).addEntry( | |
TableLootEntry.builder(this.ENCHANTED_WEAPONS).acceptFunction( | |
ApplyFunctionsIfItemHasTag.getBuilder( | |
this.WOODEN_TOOLS_TAG, | |
ApplyFunctionsIfItemHasTag.getSetNameBuilder(new StringTextComponent("Yep, it's wood"), null).build() | |
) | |
) | |
) | |
) | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment