package electroblob.wizardry.client.gui.handbook; import com.google.common.collect.Streams; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonSyntaxException; import electroblob.wizardry.Wizardry; import electroblob.wizardry.client.DrawingUtils; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.renderer.GlStateManager; import net.minecraft.client.renderer.RenderHelper; import net.minecraft.client.renderer.RenderItem; import net.minecraft.item.ItemStack; import net.minecraft.item.crafting.CraftingManager; import net.minecraft.item.crafting.IRecipe; import net.minecraft.item.crafting.Ingredient; import net.minecraft.util.JsonUtils; import net.minecraft.util.ResourceLocation; import javax.annotation.Nullable; import java.util.*; class CraftingRecipe { static final int BORDER = 7; static final int TEXTURE_INSET_X = 40, TEXTURE_INSET_Y = 190; static final int WIDTH = 121, HEIGHT = 66; // Final fields are mandatory, the rest are optional private final ResourceLocation[] locations; // Derived fields, not specifically defined in JSON private List recipes; private final Set instances = new HashSet<>(); private CraftingRecipe(ResourceLocation[] locations){ this.locations = locations; } /** * Adds an instance of this recipe to the list. * * @param page The index of the single page this image is on. * @param x The x-coordinate of the top-left corner of the image, relative to the top-left corner of the GUI. * @param y The y-coordinate of the top-left corner of the image, relative to the top-left corner of the GUI. */ void addInstance(int page, int x, int y){ instances.add(new int[]{page, x, y}); } /** Removes all instances of this recipe from the list. */ void clearInstances(){ instances.clear(); } /** Called on GUI open to load the actual recipe object from the registry. This cannot be done on JSON load since * the recipes aren't necessarily loaded at that point. */ void load(){ recipes = new ArrayList<>(locations.length); for(ResourceLocation location : locations){ IRecipe recipe = CraftingManager.getRecipe(location); if(recipe == null) Wizardry.logger.warn("The recipe {} used in the wizard's handbook does not exist, it will display a blank grid instead", location); else recipes.add(recipe); } } /** * Draws all instances of this recipe that are located on the given double-page spread. * * @param font The font renderer object. * @param itemRenderer The item renderer object. * @param doublePage The double-page index of the page to be drawn. * @param left The x coordinate of the left side of the GUI. * @param top The y coordinate of the top of the GUI. */ void draw(FontRenderer font, RenderItem itemRenderer, int doublePage, int left, int top){ int index = (int)(Minecraft.getSystemTime() % Integer.MAX_VALUE)/2000; for(int[] instance : instances){ if(GuiWizardHandbook.singleToDoublePage(instance[0]) == doublePage){ renderCraftingRecipe(font, itemRenderer, left + instance[1], top + instance[2], recipes.isEmpty() ? null : recipes.get(index % recipes.size())); } } } /** * Draws the tooltips for all instances of this recipe that are located on the given double-page spread. This has to * be done separately so that the tooltips are on top of everything else. * * @param itemRenderer The item renderer object. * @param doublePage The double-page index of the page to be drawn. * @param left The x coordinate of the left side of the GUI. * @param top The y coordinate of the top of the GUI. */ void drawTooltips(GuiWizardHandbook gui, FontRenderer font, RenderItem itemRenderer, int doublePage, int left, int top, int mouseX, int mouseY){ if(recipes.isEmpty()) return; int index = (int)(Minecraft.getSystemTime() % Integer.MAX_VALUE)/2000; for(int[] instance : instances){ if(GuiWizardHandbook.singleToDoublePage(instance[0]) == doublePage){ renderCraftingTooltips(gui, itemRenderer, left + instance[1], top + instance[2], mouseX, mouseY, recipes.get(index % recipes.size())); } } } /** * Parses the given JSON object and constructs a new {@code Image} from it, setting all the relevant fields * and references. * * @param json A JSON object representing the image to be constructed. This must contain at least a "locations" * string. * @return The resulting {@code Image} object. * @throws JsonSyntaxException if at any point the JSON object is found to be invalid. */ static CraftingRecipe fromJson(JsonObject json){ ResourceLocation[] locations = Streams.stream(JsonUtils.getJsonArray(json, "locations")) .map(je -> new ResourceLocation(je.getAsString())).toArray(ResourceLocation[]::new); return new CraftingRecipe(locations); } static void populate(Map map, JsonObject json){ JsonObject sectionsObject = JsonUtils.getJsonObject(json, "recipes"); // Need to iterate over these since we don't know what they're called or how many there are for(Map.Entry entry : sectionsObject.entrySet()){ String key = entry.getKey(); // Find out what each element is called, this will be the sections map key CraftingRecipe recipe = fromJson(entry.getValue().getAsJsonObject()); map.put(key, recipe); } } private static void renderCraftingRecipe(FontRenderer font, RenderItem itemRenderer, int x, int y, @Nullable IRecipe recipe){ GlStateManager.color(1, 1, 1, 1); Minecraft.getMinecraft().renderEngine.bindTexture(GuiWizardHandbook.texture); DrawingUtils.drawTexturedRect(x, y, TEXTURE_INSET_X, TEXTURE_INSET_Y, WIDTH, HEIGHT, GuiWizardHandbook.TEXTURE_WIDTH, GuiWizardHandbook.TEXTURE_HEIGHT); if(recipe != null){ GlStateManager.pushMatrix(); RenderHelper.enableGUIStandardItemLighting(); GlStateManager.disableLighting(); GlStateManager.enableRescaleNormal(); GlStateManager.enableColorMaterial(); itemRenderer.zLevel = 100.0F; int index = (int)(Minecraft.getSystemTime() % Integer.MAX_VALUE) / 2000; int i = 0; for(Ingredient ingredient : recipe.getIngredients()){ if(ingredient != Ingredient.EMPTY){ ItemStack stack = ingredient.getMatchingStacks()[index % ingredient.getMatchingStacks().length]; if(!stack.isEmpty()){ itemRenderer.renderItemAndEffectIntoGUI(stack, x + BORDER + 18 * (i % 3), y + BORDER + 18 * (i / 3)); itemRenderer.renderItemOverlays(font, stack, x + BORDER + 18 * (i % 3), y + BORDER + 18 * (i / 3)); } } i++; } ItemStack result = recipe.getRecipeOutput(); if(!result.isEmpty()){ itemRenderer.renderItemAndEffectIntoGUI(result, x + BORDER + 86, y + BORDER + 18); itemRenderer.renderItemOverlays(font, result, x + BORDER + 86, y + BORDER + 18); } GlStateManager.popMatrix(); GlStateManager.enableDepth(); GlStateManager.disableColorMaterial(); itemRenderer.zLevel = 0.0F; RenderHelper.disableStandardItemLighting(); } } private static void renderCraftingTooltips(GuiWizardHandbook gui, RenderItem itemRenderer, int x, int y, int mouseX, int mouseY, IRecipe recipe){ ItemStack result = recipe.getRecipeOutput(); GlStateManager.pushMatrix(); RenderHelper.enableGUIStandardItemLighting(); GlStateManager.disableLighting(); GlStateManager.enableRescaleNormal(); GlStateManager.enableColorMaterial(); itemRenderer.zLevel = 0.0F; int index = (int)(Minecraft.getSystemTime() % Integer.MAX_VALUE)/2000; int i = 0; for(Ingredient ingredient : recipe.getIngredients()){ if(ingredient != Ingredient.EMPTY){ ItemStack stack = ingredient.getMatchingStacks()[index % ingredient.getMatchingStacks().length]; if(!stack.isEmpty() && isPointInRegion(x + BORDER + 18 * (i%3), y + BORDER + 18 * (i/3), 16, 16, mouseX, mouseY)){ gui.renderToolTip(stack, mouseX, mouseY); } } i++; } if(!result.isEmpty() && isPointInRegion(x + BORDER + 86, y + BORDER + 18, 16, 16, mouseX, mouseY)){ gui.renderToolTip(result, mouseX, mouseY); } GlStateManager.popMatrix(); GlStateManager.enableDepth(); GlStateManager.disableColorMaterial(); RenderHelper.disableStandardItemLighting(); } private static boolean isPointInRegion(int left, int top, int width, int height, int mouseX, int mouseY){ return mouseX >= left - 1 && mouseX < left + width + 1 && mouseY >= top - 1 && mouseY < top + height + 1; } }