Skip to content

Commit

Permalink
Add initial implementation of loot table parser for JEI outputs
Browse files Browse the repository at this point in the history
  • Loading branch information
TeamSpen210 committed Apr 18, 2021
1 parent 4ffceb8 commit a8e39dd
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 7 deletions.
Expand Up @@ -107,6 +107,10 @@ public enum ConvertType {
* Loot table pools used for item generation.
*/
private final List<LootPool> lootPools;
// For JEI as well, cached list of items the pools generate.
@Nullable
private List<LootResult>lootResult;

/**
* Properties to assign to the result, unparsed.
* If value == FROM_INPUT, copy over.
Expand Down Expand Up @@ -134,6 +138,7 @@ public AnvilRecipe(
this.transformResult = transformResult;
this.properties = properties;
this.itemIngredientCount = (int)ingredients.stream().filter(obj -> !(obj instanceof BlockIngredient)).count();
this.lootResult = null;
}

public static List<AnvilRecipe> getSortedRecipes(@Nonnull World world) {
Expand Down Expand Up @@ -414,6 +419,16 @@ public int getItemIngredientCount() {
return itemIngredientCount;
}

/**
* Navigate through the loot pools, collecting potential output items.
*/
public List<LootResult> getRepresentativeLoot() {
if (lootResult == null) {
lootResult = LootResult.computePoolItems(lootPools);
}
return lootResult;
}

/**
* The recipe book cannot accept this.
*/
Expand Down
@@ -0,0 +1,138 @@
package knightminer.inspirations.library.recipe.anvil;

import knightminer.inspirations.Inspirations;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.loot.BinomialRange;
import net.minecraft.loot.ConstantRange;
import net.minecraft.loot.EmptyLootEntry;
import net.minecraft.loot.IRandomRange;
import net.minecraft.loot.ItemLootEntry;
import net.minecraft.loot.LootEntry;
import net.minecraft.loot.LootPool;
import net.minecraft.loot.ParentedLootEntry;
import net.minecraft.loot.RandomValueRange;
import net.minecraft.loot.TagLootEntry;
import net.minecraft.loot.conditions.ILootCondition;
import net.minecraft.loot.functions.ILootFunction;
import net.minecraft.loot.functions.SetCount;
import net.minecraft.loot.functions.SetDamage;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TranslationTextComponent;
import net.minecraftforge.fml.common.ObfuscationReflectionHelper;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
* Walk through loot table pools to produce a list of potential items.
*/
public class LootResult {
public static final String TRANSLATION = "inspirations.lootpool.";

private final ItemStack item;
private final List<ITextComponent> tooltips;

// Can't use ATs, have to reflect.
private static final Field ENTRIES = ObfuscationReflectionHelper.findField(LootPool.class, "field_186453_a");
private static final Field CONDITIONS = ObfuscationReflectionHelper.findField(LootPool.class, "field_186454_b");

public LootResult(ItemStack item) {
this.item = item;
tooltips = new ArrayList<>();
}

public LootResult(Item item) {
this(new ItemStack(item));
}

public ItemStack getStack() {
return item;
}

public List<ITextComponent> getTooltips() {
return tooltips;
}

public static List<LootResult> computePoolItems(List<LootPool> pools) {
return pools.stream()
.flatMap(LootResult::getItems)
.collect(Collectors.toList());
}

@SuppressWarnings("unchecked")
private static Stream<LootResult> getItems(LootPool pool) {
List<LootEntry> entries;
List<ILootCondition> conditions;
List<ILootFunction> functions = Arrays.asList(pool.functions);
try {
entries = (List<LootEntry>) ENTRIES.get(pool);
conditions = (List<ILootCondition>) CONDITIONS.get(pool);
} catch(IllegalAccessException e) {
return Stream.empty();
}
return entries.stream().flatMap(LootResult::parseEntry).map((res) -> applyFunctions(functions, res));
}

private static Stream<LootResult> parseEntry(LootEntry entry) {
if (entry instanceof ItemLootEntry) {
return Stream.of(applyFunctions(
Arrays.asList(((ItemLootEntry) entry).functions),
new LootResult(((ItemLootEntry) entry).item)
));
} else if (entry instanceof TagLootEntry) {
List<ILootFunction> funcs = Arrays.asList(((TagLootEntry) entry).functions);
return ((TagLootEntry) entry).tag
.getAllElements()
.stream()
.map(item -> applyFunctions(funcs, new LootResult(item)));
} else if (entry instanceof EmptyLootEntry) {
return Stream.empty();
} else if (entry instanceof ParentedLootEntry) {
return Arrays.stream(((ParentedLootEntry) entry).entries).flatMap(LootResult::parseEntry);
} else {
Inspirations.log.warn(String.format("Unknown loot entry %s!", entry.getClass().getName()));
return Stream.empty();
}
}

private static LootResult applyFunctions(List<ILootFunction> functions, LootResult res) {
for (ILootFunction func: functions) {
if (func instanceof SetCount) {
IRandomRange range = ((SetCount) func).countRange;
if (range instanceof ConstantRange) {
res.getStack().setCount(range.generateInt(new Random()));
} else if (range instanceof BinomialRange) {
res.getTooltips().add(new TranslationTextComponent(
TRANSLATION + "count.binomial",
((BinomialRange) range).p,
((BinomialRange) range).n
));
} else if (range instanceof RandomValueRange) {
res.getStack().setCount(MathHelper.floor(((RandomValueRange) range).getMin()));
res.getTooltips().add(new TranslationTextComponent(
TRANSLATION + "count.uniform",
MathHelper.floor(((RandomValueRange) range).getMin()),
MathHelper.floor(((RandomValueRange) range).getMax())
));
}
} else if (func instanceof SetDamage) {
res.getTooltips().add(new TranslationTextComponent(
TRANSLATION + "damage",
MathHelper.floor(((SetDamage) func).damageRange.getMin()),
MathHelper.floor(((SetDamage) func).damageRange.getMax()),
res.getStack().getMaxDamage()
));
} else {
Inspirations.log.warn(String.format("Unknown loot function %s!", func.getFunctionType().getClass().getName()));
}
}
return res;
}
}
Expand Up @@ -32,15 +32,13 @@
import net.minecraft.item.crafting.IRecipe;
import net.minecraft.item.crafting.RecipeManager;
import net.minecraft.util.IItemProvider;
import net.minecraft.util.IWorldPosCallable;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.registries.ForgeRegistries;
import slimeknights.mantle.item.RetexturedBlockItem;
import slimeknights.mantle.recipe.IMultiRecipe;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
Expand Down Expand Up @@ -163,6 +161,9 @@ public void registerRecipeCatalysts(IRecipeCatalystRegistration registration) {
if (Config.cauldronRecipes.getAsBoolean()) {
registration.addRecipeCatalyst(new ItemStack(Blocks.CAULDRON), CauldronCategory.ID);
}
if (Config.enableAnvilSmashing.getAsBoolean()) {
registration.addRecipeCatalyst(new ItemStack(Blocks.ANVIL), AnvilCategory.ID);
}
}

@Override
Expand Down Expand Up @@ -211,7 +212,6 @@ private static void updateHiddenItems() {
}
}


/** Data object for state of a hidable object */
private static class HideState {
private final ItemStack stack;
Expand Down
Expand Up @@ -4,10 +4,10 @@
import knightminer.inspirations.Inspirations;
import knightminer.inspirations.library.recipe.BlockIngredient;
import knightminer.inspirations.library.recipe.anvil.AnvilRecipe;
import knightminer.inspirations.library.recipe.anvil.LootResult;
import mezz.jei.api.constants.VanillaTypes;
import mezz.jei.api.gui.IRecipeLayout;
import mezz.jei.api.gui.drawable.IDrawable;
import mezz.jei.api.gui.drawable.IDrawableAnimated;
import mezz.jei.api.gui.ingredient.IGuiItemStackGroup;
import mezz.jei.api.helpers.IGuiHelper;
import mezz.jei.api.ingredients.IIngredients;
Expand All @@ -28,8 +28,6 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public class AnvilCategory implements IRecipeCategory<AnvilRecipe> {
/** Unique ID for this category */
Expand Down Expand Up @@ -116,8 +114,14 @@ public void setIngredients(AnvilRecipe recipe, IIngredients ingredients) {
} else {
outputBlocks = Collections.emptyList();
}

inputItems.add(0, inputBlocks);
outputItems.add(0, outputBlocks);

for(LootResult lootResult : recipe.getRepresentativeLoot()) {
outputItems.add(Collections.singletonList(lootResult.getStack()));
}

ingredients.setInputLists(VanillaTypes.ITEM, inputItems);
ingredients.setOutputLists(VanillaTypes.ITEM, outputItems);
}
Expand All @@ -127,6 +131,7 @@ public void setRecipe(IRecipeLayout layout, AnvilRecipe recipe, IIngredients ing
IGuiItemStackGroup items = layout.getItemStacks();
List<List<ItemStack>> inputs = ingredients.getInputs(VanillaTypes.ITEM);
List<List<ItemStack>> outputs = ingredients.getOutputs(VanillaTypes.ITEM);
List<LootResult> loot = recipe.getRepresentativeLoot();

if (inputs.size() == 0 || outputs.size() == 0) {
throw new IllegalArgumentException("Must have input or output block list.");
Expand All @@ -142,6 +147,18 @@ public void setRecipe(IRecipeLayout layout, AnvilRecipe recipe, IIngredients ing
items.set(slot, inputs.get(i + 1));
slot++;
}
int outputStart = slot;
for(int i = 0; i < loot.size(); i++) {
items.init(slot, false, 127 + 18 * (i % 3), 19 - 18 * (i / 3));
items.set(slot, loot.get(i).getStack());
slot++;
}
items.addTooltipCallback((slotIndex, input, ingredient, tooltip) -> {
slotIndex -= outputStart;
if (0 <= slotIndex && slotIndex < loot.size()) {
tooltip.addAll(loot.get(slotIndex).getTooltips());
}
});
}

@Override
Expand All @@ -150,11 +167,17 @@ public void draw(AnvilRecipe recipe, MatrixStack matrices, double mouseX, double
destroyIcon.draw(matrices, BLOCK_OUT_X + 1, BLOCK_Y + 1);
}
int inputCount = recipe.getItemIngredientCount();
int outputCount = recipe.getRepresentativeLoot().size();
if (inputCount > 3) {
slots_inp_2.draw(matrices, 0, 0);
} else if (inputCount > 0) {
slots_inp_1.draw(matrices, 0, 18);
}
if (outputCount > 3) {
slots_out_2.draw(matrices, 126, 0);
} else if (outputCount > 0) {
slots_out_1.draw(matrices, 126, 18);
}
}

@Override
Expand All @@ -171,5 +194,4 @@ public List<ITextComponent> getTooltipStrings(AnvilRecipe recipe, double mouseX,
public Class<? extends AnvilRecipe> getRecipeClass() {
return AnvilRecipe.class;
}

}
11 changes: 11 additions & 0 deletions src/main/resources/META-INF/accesstransformer.cfg
Expand Up @@ -44,6 +44,17 @@ protected net.minecraft.data.loot.BlockLootTables field_218575_c # SHEARS
protected net.minecraft.data.loot.BlockLootTables field_218576_d # SILK_TOUCH_OR_SHEARS
protected net.minecraft.data.loot.BlockLootTables field_218577_e # NOT_SILK_TOUCH_OR_SHEARS

# Needed to introspect the loot pools.
public-f net.minecraft.loot.LootPool field_216102_d # functions
public-f net.minecraft.loot.ItemLootEntry field_186368_a # item
public-f net.minecraft.loot.TagLootEntry field_216180_c # tag
public-f net.minecraft.loot.StandaloneLootEntry field_216160_g # functions
public net.minecraft.loot.ParentedLootEntry field_216147_c # entries
public-f net.minecraft.loot.functions.SetCount field_186568_a # countRange
public-f net.minecraft.loot.BinomialRange field_215841_d # n
public-f net.minecraft.loot.BinomialRange field_215842_e # p
public-f net.minecraft.loot.functions.SetDamage field_186566_b # damageRange

# Unsaddling pigs
public net.minecraft.entity.passive.PigEntity field_234214_bx_ # field_234214_bx_

Expand Down
4 changes: 4 additions & 0 deletions src/main/resources/assets/inspirations/lang/en_us.json
Expand Up @@ -183,6 +183,10 @@
"jei.inspirations.anvil_smashing": "Anvil Smashing",
"jei.inspirations.anvil_smashing.destroys": "Block is destroyed",

"inspirations.lootpool.count.uniform": "Random Count: %s-%s",
"inspirations.lootpool.count.binomial": "Random Count: %s%%, tried %s times",
"inspirations.lootpool.damage": "Damage: %s-%s / %s",

"jei.inspirations.cauldron": "Cauldron",
"jei.inspirations.cauldron.time": "%s s",
"jei.inspirations.cauldron.level": "%s/12 full",
Expand Down

0 comments on commit a8e39dd

Please sign in to comment.