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 00000000..135f37cd Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/integration/jei/imbuement_altar_background.png differ