From eeb408c68c0fd3b5e911e4c7c49f2fc0cd089b8e Mon Sep 17 00:00:00 2001 From: Electroblob77 <35599699+Electroblob77@users.noreply.github.com> Date: Mon, 21 Sep 2020 13:55:55 +0100 Subject: [PATCH] Add imbuement altar 'recipes' to JEI --- .../integration/jei/ImbuementAltarRecipe.java | 49 +++++ .../jei/ImbuementAltarRecipeCategory.java | 188 ++++++++++++++++++ .../integration/jei/WizardryJEIPlugin.java | 7 +- .../tileentity/TileEntityImbuementAltar.java | 111 ++++++----- .../assets/ebwizardry/lang/en_gb.lang | 1 + .../assets/ebwizardry/lang/en_us.lang | 1 + .../jei/imbuement_altar_background.png | Bin 0 -> 12609 bytes 7 files changed, 310 insertions(+), 47 deletions(-) create mode 100644 src/main/java/electroblob/wizardry/integration/jei/ImbuementAltarRecipe.java create mode 100644 src/main/java/electroblob/wizardry/integration/jei/ImbuementAltarRecipeCategory.java create mode 100644 src/main/resources/assets/ebwizardry/textures/integration/jei/imbuement_altar_background.png diff --git a/src/main/java/electroblob/wizardry/integration/jei/ImbuementAltarRecipe.java b/src/main/java/electroblob/wizardry/integration/jei/ImbuementAltarRecipe.java new file mode 100644 index 00000000..eb7cc84e --- /dev/null +++ b/src/main/java/electroblob/wizardry/integration/jei/ImbuementAltarRecipe.java @@ -0,0 +1,49 @@ +package electroblob.wizardry.integration.jei; + +import electroblob.wizardry.Wizardry; +import mezz.jei.api.ingredients.IIngredients; +import mezz.jei.api.ingredients.VanillaTypes; +import mezz.jei.api.recipe.IRecipeWrapper; +import net.minecraft.item.ItemStack; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Represents a 'recipe' for the imbuement altar. + * + * @author Electroblob + * @since Wizardry 4.3 + */ +public class ImbuementAltarRecipe implements IRecipeWrapper { + + private final ItemStack result; + + private final List> inputs; + + public ImbuementAltarRecipe(ItemStack centreStack, List> dusts, ItemStack result){ + + this.result = result; + + this.inputs = new ArrayList<>(); + this.inputs.add(Collections.singletonList(centreStack)); + + if(dusts.size() > 4){ + Wizardry.logger.warn("Tried to create an imbuement altar JEI recipe with more than 4 dust stacks, ignoring the rest"); + dusts = dusts.subList(0, 4); + }else if(dusts.size() < 4){ + throw new IllegalArgumentException("Imbuement altar recipes require 4 stacks of dust"); + } + + this.inputs.addAll(dusts); + + } + + @Override + public void getIngredients(IIngredients ingredients){ + ingredients.setInputLists(VanillaTypes.ITEM, inputs); + ingredients.setOutput(VanillaTypes.ITEM, result); + } + +} diff --git a/src/main/java/electroblob/wizardry/integration/jei/ImbuementAltarRecipeCategory.java b/src/main/java/electroblob/wizardry/integration/jei/ImbuementAltarRecipeCategory.java new file mode 100644 index 00000000..0144ab84 --- /dev/null +++ b/src/main/java/electroblob/wizardry/integration/jei/ImbuementAltarRecipeCategory.java @@ -0,0 +1,188 @@ +package electroblob.wizardry.integration.jei; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.constants.Element; +import electroblob.wizardry.item.ItemWizardArmour; +import electroblob.wizardry.registry.WizardryBlocks; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.tileentity.TileEntityImbuementAltar; +import mezz.jei.api.IGuiHelper; +import mezz.jei.api.gui.IDrawable; +import mezz.jei.api.gui.IGuiItemStackGroup; +import mezz.jei.api.gui.IRecipeLayout; +import mezz.jei.api.ingredients.IIngredients; +import mezz.jei.api.ingredients.VanillaTypes; +import mezz.jei.api.recipe.IRecipeCategory; +import mezz.jei.api.recipe.IRecipeCategoryRegistration; +import net.minecraft.client.resources.I18n; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.util.NonNullList; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.oredict.OreDictionary; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** + * JEI recipe category implementation for all 'recipes' in the imbuement altar. + * + * @author Electroblob + * @since Wizardry 4.3 + */ +public class ImbuementAltarRecipeCategory implements IRecipeCategory { + + static final String UID = "ebwizardry:imbuement_altar"; + static final ResourceLocation TEXTURE = new ResourceLocation(Wizardry.MODID, "textures/integration/jei/imbuement_altar_background.png"); + + static final int WIDTH = 134; + static final int HEIGHT = 74; + // Annoyingly, these seem to be for 18x18 slots rather than the actual highlighted area, unlike container slots + static final int CENTRE_SLOT_X = 28; + static final int CENTRE_SLOT_Y = 28; + static final int SLOT_SPACING_X = 28; + static final int SLOT_SPACING_Y = 28; + static final int OUTPUT_SLOT_X = 112; + static final int OUTPUT_SLOT_Y = 28; + + private final IDrawable background; + + public ImbuementAltarRecipeCategory(IRecipeCategoryRegistration registry){ + IGuiHelper helper = registry.getJeiHelpers().getGuiHelper(); + background = helper.createDrawable(TEXTURE, 0, 0, WIDTH, HEIGHT); + } + + @Override + public String getUid(){ + return UID; + } + + @Override + public String getTitle(){ + // JEI is client-side, so client classes can safely be used here + return I18n.format("integration.jei.category." + UID); + } + + @Override + public String getModName(){ + return Wizardry.NAME; + } + + @Override + public IDrawable getBackground(){ + return background; + } + + @Override + public void setRecipe(IRecipeLayout recipeLayout, ImbuementAltarRecipe recipeWrapper, IIngredients ingredients){ + + // Okay, they're not technically *slots* but to all intents and purposes, that's how they behave + IGuiItemStackGroup slots = recipeLayout.getItemStacks(); + + List> inputs = ingredients.getInputs(VanillaTypes.ITEM); + List> outputs = ingredients.getOutputs(VanillaTypes.ITEM); + + // Slot initialisation + int i = 0; + slots.init(i++, true, CENTRE_SLOT_X, CENTRE_SLOT_Y); // Centre + slots.init(i++, true, CENTRE_SLOT_X, CENTRE_SLOT_Y - SLOT_SPACING_Y); // Top + slots.init(i++, true, CENTRE_SLOT_X - SLOT_SPACING_X, CENTRE_SLOT_Y); // Left + slots.init(i++, true, CENTRE_SLOT_X, CENTRE_SLOT_Y + SLOT_SPACING_Y); // Bottom + slots.init(i++, true, CENTRE_SLOT_X + SLOT_SPACING_X, CENTRE_SLOT_Y); // Right + slots.init(i++, false, OUTPUT_SLOT_X, OUTPUT_SLOT_Y); // Output + + // Assign ingredients to slots + for(int j = 0; j < inputs.size(); j++) slots.set(j, inputs.get(j)); + for(int k = 0; k < outputs.size(); k++) slots.set(inputs.size() + k, outputs.get(k)); + + } + + /** Called to generate all of wizardry's imbuement altar 'recipes' for JEI. */ + public static Collection generateRecipes(){ + + List recipes = new ArrayList<>(); + + recipes.addAll(generateBookRecipes()); + recipes.addAll(generateCrystalRecipes()); + recipes.addAll(generateCrystalBlockRecipes()); + recipes.addAll(generateArmourRecipes()); + + return recipes; + + } + + private static Collection generateBookRecipes(){ + + List recipes = new ArrayList<>(); + + NonNullList variants = NonNullList.create(); + WizardryItems.spectral_dust.getSubItems(WizardryItems.spectral_dust.getCreativeTab(), variants); + List> dusts = Collections.nCopies(4, variants); + + recipes.add(new ImbuementAltarRecipe(new ItemStack(WizardryItems.ruined_spell_book), dusts, new ItemStack(WizardryItems.spell_book, 1, OreDictionary.WILDCARD_VALUE))); + + return recipes; + + } + + private static Collection generateCrystalRecipes(){ + + List recipes = new ArrayList<>(); + + ItemStack input = new ItemStack(WizardryItems.magic_crystal); + + for(int meta = 1; meta < Element.values().length; meta++){ + List> dusts = Collections.nCopies(4, Collections.singletonList(new ItemStack(WizardryItems.spectral_dust, 1, meta))); + ItemStack output = new ItemStack(WizardryItems.magic_crystal, 1, meta); + recipes.add(new ImbuementAltarRecipe(input, dusts, output)); + } + + return recipes; + + } + + private static Collection generateCrystalBlockRecipes(){ + + List recipes = new ArrayList<>(); + + ItemStack input = new ItemStack(WizardryBlocks.crystal_block); + + for(int meta = 1; meta < Element.values().length; meta++){ + List> dusts = Collections.nCopies(4, Collections.singletonList(new ItemStack(WizardryItems.spectral_dust, 1, meta))); + ItemStack output = new ItemStack(WizardryBlocks.crystal_block, 1, meta); + recipes.add(new ImbuementAltarRecipe(input, dusts, output)); + } + + return recipes; + + } + + private static Collection generateArmourRecipes(){ + + List recipes = new ArrayList<>(); + + for(Item item : Item.REGISTRY){ + + if(item instanceof ItemWizardArmour){ + + ItemStack input = new ItemStack(item); + + for(Element e : Element.values()){ + + if(e == Element.MAGIC) continue; + + List> dusts = Collections.nCopies(4, Collections.singletonList(new ItemStack(WizardryItems.spectral_dust, 1, e.ordinal()))); + ItemStack output = TileEntityImbuementAltar.getImbuementResult(input, new Element[]{e, e, e, e}, false, null, null); + + if(!output.isEmpty()) recipes.add(new ImbuementAltarRecipe(input, dusts, output)); + } + } + } + + return recipes; + + } + +} diff --git a/src/main/java/electroblob/wizardry/integration/jei/WizardryJEIPlugin.java b/src/main/java/electroblob/wizardry/integration/jei/WizardryJEIPlugin.java index eb58fade..104022ad 100644 --- a/src/main/java/electroblob/wizardry/integration/jei/WizardryJEIPlugin.java +++ b/src/main/java/electroblob/wizardry/integration/jei/WizardryJEIPlugin.java @@ -34,6 +34,7 @@ public class WizardryJEIPlugin implements IModPlugin { if(!Wizardry.settings.jeiIntegration) return; registry.addRecipeCategories(new ArcaneWorkbenchRecipeCategory(registry)); + registry.addRecipeCategories(new ImbuementAltarRecipeCategory(registry)); } @Override @@ -43,11 +44,13 @@ public class WizardryJEIPlugin implements IModPlugin { // Add arcane workbench as the item required to use arcane workbench recipes registry.addRecipeCatalyst(new ItemStack(WizardryBlocks.arcane_workbench), ArcaneWorkbenchRecipeCategory.UID); - registry.addRecipes(ArcaneWorkbenchRecipeCategory.generateRecipes(), ArcaneWorkbenchRecipeCategory.UID); - registry.getRecipeTransferRegistry().addRecipeTransferHandler(new ArcaneWorkbenchTransferHandler()); + // Add imbuement altar as the item required to use imbuement altar recipes + registry.addRecipeCatalyst(new ItemStack(WizardryBlocks.imbuement_altar), ImbuementAltarRecipeCategory.UID); + registry.addRecipes(ImbuementAltarRecipeCategory.generateRecipes(), ImbuementAltarRecipeCategory.UID); + // Hide conjured items from JEI IIngredientBlacklist blacklist = registry.getJeiHelpers().getIngredientBlacklist(); for(Item item : Item.REGISTRY){ diff --git a/src/main/java/electroblob/wizardry/tileentity/TileEntityImbuementAltar.java b/src/main/java/electroblob/wizardry/tileentity/TileEntityImbuementAltar.java index 2bc96462..b101bd11 100644 --- a/src/main/java/electroblob/wizardry/tileentity/TileEntityImbuementAltar.java +++ b/src/main/java/electroblob/wizardry/tileentity/TileEntityImbuementAltar.java @@ -22,6 +22,7 @@ import net.minecraft.util.EnumFacing; import net.minecraft.util.ITickable; import net.minecraft.util.SoundCategory; import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; import net.minecraft.world.WorldServer; import net.minecraft.world.storage.loot.LootContext; import net.minecraft.world.storage.loot.LootTable; @@ -134,55 +135,20 @@ public class TileEntityImbuementAltar extends TileEntity implements ITickable { private ItemStack getResult(){ - if(stack.getItem() instanceof ItemWizardArmour && ((ItemWizardArmour)stack.getItem()).element == null){ + boolean actuallyCrafting = imbuementTimer >= IMBUEMENT_DURATION - 1 && world instanceof WorldServer; + Element[] elements = getReceptacleElements(); - Element[] elements = getReceptacleElements(); + ItemStack result = getImbuementResult(stack, elements, actuallyCrafting, world, lastUser); - if(Arrays.stream(elements).distinct().count() == 1 && elements[0] != null){ // All the same element - - ItemStack result = new ItemStack(WizardryItems.getArmour(elements[0], ((ItemWizardArmour)stack.getItem()).armorType)); - displayElement = elements[0]; - - result.setTagCompound(stack.getTagCompound()); - ((IManaStoringItem)result.getItem()).setMana(result, ((ItemWizardArmour)stack.getItem()).getMana(stack)); - - return result; - } + if(result.isEmpty()){ + displayElement = null; + }else if(Arrays.stream(elements).distinct().count() == 1){ // All the same element + displayElement = elements[0]; + }else{ + displayElement = Element.MAGIC; } - if((stack.getItem() == WizardryItems.magic_crystal || stack.getItem() == Item.getItemFromBlock(WizardryBlocks.crystal_block)) - && stack.getMetadata() == 0){ - - Element[] elements = getReceptacleElements(); - - if(Arrays.stream(elements).distinct().count() == 1 && elements[0] != null){ // All the same element - displayElement = elements[0]; - return new ItemStack(stack.getItem(), stack.getCount(), elements[0].ordinal()); - } - } - - if(stack.getItem() == WizardryItems.ruined_spell_book){ - - if(!ArrayUtils.contains(getReceptacleElements(), null)){ // All receptacles filled (any element) - - if(imbuementTimer >= IMBUEMENT_DURATION - 1 && world instanceof WorldServer){ - - LootTable table = this.world.getLootTableManager().getLootTableFromLocation(WizardryLoot.RUINED_SPELL_BOOK_LOOT_TABLE); - LootContext context = new LootContext.Builder((WorldServer)world).withPlayer(lastUser) - .withLuck(lastUser == null ? 0 : lastUser.getLuck()).build(); - - List stacks = table.generateLootForPools(world.rand, context); - return stacks.isEmpty() ? ItemStack.EMPTY : stacks.get(0); - } - - displayElement = Element.MAGIC; - - return new ItemStack(WizardryItems.spell_book); // No point generating loot every tick just to check the recipe - } - } - - displayElement = null; - return ItemStack.EMPTY; + return result; } /** Returns the elements of the 4 adjacent receptacles, in SWNE order. Null means an empty or missing receptacle. */ @@ -253,4 +219,59 @@ public class TileEntityImbuementAltar extends TileEntity implements ITickable { readFromNBT(pkt.getNbtCompound()); } + /** + * Returns the stack that results from imbuing the given input with the given elements. + * @param input The item stack being imbued + * @param receptacleElements The elements of the four receptacles + * @param fullLootGen True to perform full loot generation from loot tables, false if this is just being queried for + * visuals or other purposes. + * @param world A reference to the current world object (may be null if {@code fullLootGen} is false) + * @param lastUser The player that last interacted with the imbuement altar, or null if there isn't one (or if this + * is being queried for other reasons, e.g. JEI) + * @return The resulting item stack, or an empty stack if the given combination is not a valid imbuement + */ + public static ItemStack getImbuementResult(ItemStack input, Element[] receptacleElements, boolean fullLootGen, World world, EntityPlayer lastUser){ + + if(input.getItem() instanceof ItemWizardArmour && ((ItemWizardArmour)input.getItem()).element == null){ + + if(Arrays.stream(receptacleElements).distinct().count() == 1 && receptacleElements[0] != null){ // All the same element + + ItemStack result = new ItemStack(WizardryItems.getArmour(receptacleElements[0], ((ItemWizardArmour)input.getItem()).armorType)); + + result.setTagCompound(input.getTagCompound()); + ((IManaStoringItem)result.getItem()).setMana(result, ((ItemWizardArmour)input.getItem()).getMana(input)); + + return result; + } + } + + if((input.getItem() == WizardryItems.magic_crystal || input.getItem() == Item.getItemFromBlock(WizardryBlocks.crystal_block)) + && input.getMetadata() == 0){ + + if(Arrays.stream(receptacleElements).distinct().count() == 1 && receptacleElements[0] != null){ // All the same element + return new ItemStack(input.getItem(), input.getCount(), receptacleElements[0].ordinal()); + } + } + + if(input.getItem() == WizardryItems.ruined_spell_book){ + + if(!ArrayUtils.contains(receptacleElements, null)){ // All receptacles filled (any element) + + if(fullLootGen){ + + LootTable table = world.getLootTableManager().getLootTableFromLocation(WizardryLoot.RUINED_SPELL_BOOK_LOOT_TABLE); + LootContext context = new LootContext.Builder((WorldServer)world).withPlayer(lastUser) + .withLuck(lastUser == null ? 0 : lastUser.getLuck()).build(); + + List stacks = table.generateLootForPools(world.rand, context); + return stacks.isEmpty() ? ItemStack.EMPTY : stacks.get(0); + } + + return new ItemStack(WizardryItems.spell_book); // No point generating loot every tick just to check the recipe + } + } + + return ItemStack.EMPTY; + } + } diff --git a/src/main/resources/assets/ebwizardry/lang/en_gb.lang b/src/main/resources/assets/ebwizardry/lang/en_gb.lang index 45d5863b..02d857cb 100644 --- a/src/main/resources/assets/ebwizardry/lang/en_gb.lang +++ b/src/main/resources/assets/ebwizardry/lang/en_gb.lang @@ -1592,6 +1592,7 @@ config.ebwizardry.auto_place_underground_library_markers=Auto-Place Underground config.ebwizardry.auto_place_underground_library_markers.tooltip=Controls whether wizardry automatically places antique atlas markers at the locations of underground library ruins. integration.jei.category.ebwizardry\:arcane_workbench=Arcane Workbench +integration.jei.category.ebwizardry\:imbuement_altar=Imbuement Altar integration.antiqueatlas.marker.ebwizardry.wizard_tower=Wizard Tower integration.antiqueatlas.marker.ebwizardry.obelisk=Obelisk diff --git a/src/main/resources/assets/ebwizardry/lang/en_us.lang b/src/main/resources/assets/ebwizardry/lang/en_us.lang index 223433fe..17db89c7 100644 --- a/src/main/resources/assets/ebwizardry/lang/en_us.lang +++ b/src/main/resources/assets/ebwizardry/lang/en_us.lang @@ -1592,6 +1592,7 @@ config.ebwizardry.auto_place_underground_library_markers=Auto-Place Underground config.ebwizardry.auto_place_underground_library_markers.tooltip=Controls whether wizardry automatically places antique atlas markers at the locations of underground library ruins. integration.jei.category.ebwizardry\:arcane_workbench=Arcane Workbench +integration.jei.category.ebwizardry\:imbuement_altar=Imbuement Altar integration.antiqueatlas.marker.ebwizardry.wizard_tower=Wizard Tower integration.antiqueatlas.marker.ebwizardry.obelisk=Obelisk diff --git a/src/main/resources/assets/ebwizardry/textures/integration/jei/imbuement_altar_background.png b/src/main/resources/assets/ebwizardry/textures/integration/jei/imbuement_altar_background.png new file mode 100644 index 0000000000000000000000000000000000000000..135f37cd063492ef8cb13f392130ca1c27d8ec0c GIT binary patch literal 12609 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