From 13569058ae2144869f6dd61f91a6ea766b295211 Mon Sep 17 00:00:00 2001 From: Electroblob77 <35599699+Electroblob77@users.noreply.github.com> Date: Sat, 5 Jan 2019 22:38:06 +0000 Subject: [PATCH] Handbook rewrite part 1: Formatting and JSON-ification! - Everything is now defined in a JSON file - Formatting is now done dynamically and content wraps properly no matter what font is used - Added inline hyperlinks - Added a movable bookmark for quick access to a specific page - Added next section and previous section buttons to flip through entire sections at once - Added a contents button visible on every page, which navigates directly to the main contents --- .../wizardry/client/gui/GuiButtonApply.java | 42 -- .../client/gui/GuiButtonInvisible.java | 2 +- .../client/gui/GuiButtonTurnPage.java | 47 -- .../client/gui/GuiWizardHandbook.java | 695 ------------------ .../client/gui/handbook/Contents.java | 181 +++++ .../client/gui/handbook/CraftingRecipe.java | 225 ++++++ .../gui/handbook/GuiButtonHyperlink.java | 179 +++++ .../gui/handbook/GuiButtonTurnPage.java | 63 ++ .../gui/handbook/GuiWizardHandbook.java | 566 ++++++++++++++ .../wizardry/client/gui/handbook/Image.java | 119 +++ .../wizardry/client/gui/handbook/Section.java | 429 +++++++++++ .../ebwizardry/texts/handbook_en_gb.json | 528 +++++++++++++ .../ebwizardry/texts/handbook_en_us.json | 528 +++++++++++++ .../textures/gui/arcane_workbench_picture.png | Bin 0 -> 15386 bytes .../textures/gui/flower_picture.png | Bin 0 -> 86005 bytes .../textures/gui/handbook_recipes.png | Bin 26539 -> 0 bytes 16 files changed, 2819 insertions(+), 785 deletions(-) delete mode 100644 src/main/java/electroblob/wizardry/client/gui/GuiButtonApply.java delete mode 100644 src/main/java/electroblob/wizardry/client/gui/GuiButtonTurnPage.java delete mode 100644 src/main/java/electroblob/wizardry/client/gui/GuiWizardHandbook.java create mode 100644 src/main/java/electroblob/wizardry/client/gui/handbook/Contents.java create mode 100644 src/main/java/electroblob/wizardry/client/gui/handbook/CraftingRecipe.java create mode 100644 src/main/java/electroblob/wizardry/client/gui/handbook/GuiButtonHyperlink.java create mode 100644 src/main/java/electroblob/wizardry/client/gui/handbook/GuiButtonTurnPage.java create mode 100644 src/main/java/electroblob/wizardry/client/gui/handbook/GuiWizardHandbook.java create mode 100644 src/main/java/electroblob/wizardry/client/gui/handbook/Image.java create mode 100644 src/main/java/electroblob/wizardry/client/gui/handbook/Section.java create mode 100644 src/main/resources/assets/ebwizardry/texts/handbook_en_gb.json create mode 100644 src/main/resources/assets/ebwizardry/texts/handbook_en_us.json create mode 100644 src/main/resources/assets/ebwizardry/textures/gui/arcane_workbench_picture.png create mode 100644 src/main/resources/assets/ebwizardry/textures/gui/flower_picture.png delete mode 100644 src/main/resources/assets/ebwizardry/textures/gui/handbook_recipes.png diff --git a/src/main/java/electroblob/wizardry/client/gui/GuiButtonApply.java b/src/main/java/electroblob/wizardry/client/gui/GuiButtonApply.java deleted file mode 100644 index a18d9668..00000000 --- a/src/main/java/electroblob/wizardry/client/gui/GuiButtonApply.java +++ /dev/null @@ -1,42 +0,0 @@ -package electroblob.wizardry.client.gui; - -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiButton; -import net.minecraft.client.resources.I18n; -import net.minecraftforge.fml.relauncher.Side; -import net.minecraftforge.fml.relauncher.SideOnly; - -@SideOnly(Side.CLIENT) -class GuiButtonApply extends GuiButton { - - public GuiButtonApply(int id, int x, int y){ - super(id, x, y, 32, 16, I18n.format("container." + Wizardry.MODID + ":arcane_workbench.apply")); - } - - @Override - public void drawButton(Minecraft minecraft, int mouseX, int mouseY, float partialTicks){ - - // Whether the button is highlighted - this.hovered = mouseX >= this.x && mouseY >= this.y && mouseX < this.x + this.width && mouseY < this.y + this.height; - - int k = 36; - int l = 220; - int colour = 14737632; - - if(this.enabled){ - if(this.hovered){ - k += this.width * 2; - colour = 16777120; - } - }else{ - k += this.width; - colour = 10526880; - } - - WizardryUtilities.drawTexturedRect(this.x, this.y, k, l, this.width, this.height, 256, 256); - this.drawCenteredString(minecraft.fontRenderer, this.displayString, this.x + this.width / 2, - this.y + (this.height - 8) / 2, colour); - } -} \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/client/gui/GuiButtonInvisible.java b/src/main/java/electroblob/wizardry/client/gui/GuiButtonInvisible.java index c80db3af..a65e71da 100644 --- a/src/main/java/electroblob/wizardry/client/gui/GuiButtonInvisible.java +++ b/src/main/java/electroblob/wizardry/client/gui/GuiButtonInvisible.java @@ -6,7 +6,7 @@ import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; @SideOnly(Side.CLIENT) -class GuiButtonInvisible extends GuiButton { +public class GuiButtonInvisible extends GuiButton { public GuiButtonInvisible(int id, int x, int y, int width, int height){ super(id, x, y, width, height, ""); diff --git a/src/main/java/electroblob/wizardry/client/gui/GuiButtonTurnPage.java b/src/main/java/electroblob/wizardry/client/gui/GuiButtonTurnPage.java deleted file mode 100644 index f9c7a8cd..00000000 --- a/src/main/java/electroblob/wizardry/client/gui/GuiButtonTurnPage.java +++ /dev/null @@ -1,47 +0,0 @@ -package electroblob.wizardry.client.gui; - -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiButton; -import net.minecraft.client.renderer.GlStateManager; -import net.minecraft.util.ResourceLocation; -import net.minecraftforge.fml.relauncher.Side; -import net.minecraftforge.fml.relauncher.SideOnly; - -@SideOnly(Side.CLIENT) -class GuiButtonTurnPage extends GuiButton { - - /** True for pointing right (next page), false for pointing left (previous page). */ - private final boolean nextPage; - - private static final ResourceLocation texture = new ResourceLocation(Wizardry.MODID, "textures/gui/handbook.png"); - - public GuiButtonTurnPage(int id, int x, int y, boolean isNextPage){ - super(id, x, y, 23, 13, ""); - this.nextPage = isNextPage; - } - - @Override - public void drawButton(Minecraft minecraft, int mouseX, int mouseY, float partialTicks){ - - if(this.visible){ - - boolean flag = mouseX >= this.x && mouseY >= this.y && mouseX < this.x + this.width && mouseY < this.y + this.height; - GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); - minecraft.getTextureManager().bindTexture(texture); - int k = 0; - int l = 192; - - if(flag){ - k += 23; - } - - if(!this.nextPage){ - l += 13; - } - - WizardryUtilities.drawTexturedRect(this.x, this.y, k, l, 23, 13, 288, 256); - } - } -} diff --git a/src/main/java/electroblob/wizardry/client/gui/GuiWizardHandbook.java b/src/main/java/electroblob/wizardry/client/gui/GuiWizardHandbook.java deleted file mode 100644 index b3e5d884..00000000 --- a/src/main/java/electroblob/wizardry/client/gui/GuiWizardHandbook.java +++ /dev/null @@ -1,695 +0,0 @@ -package electroblob.wizardry.client.gui; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; - -import org.apache.commons.lang3.tuple.ImmutablePair; -import org.apache.commons.lang3.tuple.Pair; -import org.lwjgl.input.Keyboard; -import org.lwjgl.opengl.GL11; - -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.client.ClientProxy; -import electroblob.wizardry.constants.Constants; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.registry.WizardryBlocks; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiButton; -import net.minecraft.client.gui.GuiScreen; -import net.minecraft.client.renderer.BufferBuilder; -import net.minecraft.client.renderer.GlStateManager; -import net.minecraft.client.renderer.RenderHelper; -import net.minecraft.client.renderer.Tessellator; -import net.minecraft.client.renderer.vertex.DefaultVertexFormats; -import net.minecraft.init.Blocks; -import net.minecraft.init.Items; -import net.minecraft.item.ItemStack; -import net.minecraft.util.NonNullList; -import net.minecraft.util.ResourceLocation; - -public class GuiWizardHandbook extends GuiScreen { - - private int xSize, ySize; - private int pageNumber = 0; - - private static final int PAGE_WIDTH = 120; - /** - * The integer colour for black passed into the font renderer methods. This used to be 0 but that's now white for - * some reason, so I've made a it a constant in case it changes again. - */ - // I think this is actually ever-so-slightly lighter than pure black, but the difference is unnoticeable. - private static final int BLACK = 1; - - public static final ResourceLocation regularHandbook = new ResourceLocation(Wizardry.MODID, "textures/gui/handbook.png"); - public static final ResourceLocation ore = new ResourceLocation(Wizardry.MODID, "textures/gui/ore_picture.png"); - public static final ResourceLocation crystal = new ResourceLocation(Wizardry.MODID, "textures/items/magic_crystal.png"); - public static final ResourceLocation workbenchGui = new ResourceLocation(Wizardry.MODID, "textures/gui/arcane_workbench.png"); - public static final ResourceLocation craftingGrids = new ResourceLocation(Wizardry.MODID, "textures/gui/handbook_recipes.png"); - - private List> text; - private List
sections; - - private static final List>>> RECIPES = new ArrayList<>(); - - private int guiPage, imagePage; - - public GuiWizardHandbook(){ - super(); - xSize = 288; - ySize = 180; - } - - @Override - public void drawScreen(int mouseX, int mouseY, float par3){ - - int xPos = this.width / 2 - xSize / 2; - int yPos = this.height / 2 - this.ySize / 2; - - // Tests for crafting recipes section - if(pageNumber >= (sections.get(sections.size() - 1).pageNumber - 1) / 2 - && pageNumber < (sections.get(sections.size() - 1).pageNumber - 1) / 2 + 4){ - Minecraft.getMinecraft().renderEngine.bindTexture(craftingGrids); - }else{ - Minecraft.getMinecraft().renderEngine.bindTexture(regularHandbook); - } - - WizardryUtilities.drawTexturedRect(xPos, yPos, 0, 0, xSize, ySize, xSize, 256); - - // Arcane workbench gui picture - if(pageNumber == (this.guiPage - 1) / 2){ - Minecraft.getMinecraft().renderEngine.bindTexture(workbenchGui); - this.drawTexturedModalRect(this.guiPage % 2 == 1 ? xPos + 17 : this.width / 2 + 7, yPos + 14, 28, 12, 120, - 118); - } - - // Magic crystal and crystal ore images - if(pageNumber == (this.imagePage - 1) / 2){ - - Minecraft.getMinecraft().renderEngine.bindTexture(ore); - WizardryUtilities.drawTexturedRect(this.imagePage % 2 == 1 ? xPos + 17 : this.width / 2 + 7, yPos + 80, 0, - 0, 64, 64, 64, 64); - - Minecraft.getMinecraft().renderEngine.bindTexture(crystal); - drawTexturedStretchedRect(this.imagePage % 2 == 1 ? xPos + 17 + 64 : this.width / 2 + 7 + 62, yPos + 80, 0, - 0, 64, 64, 1, 1); - - } - - this.fontRenderer.drawString("" + (pageNumber * 2 + 1), xPos + xSize / 4 - 3, yPos + ySize - 20, 0); - this.fontRenderer.drawString("" + (pageNumber * 2 + 2), xPos + 3 * xSize / 4 - 5, yPos + ySize - 20, 0); - - super.drawScreen(mouseX, mouseY, par3); - - int lineNumber = 0; - - if(pageNumber == 1){ - for(Section s : sections){ - s.drawContents(); - } - }else{ - for(Section s : sections){ - s.hideButton(); - } - } - - for(String paragraph : text.get(pageNumber * 2)){ - - this.fontRenderer.drawSplitString(paragraph, xPos + 17, - yPos + 14 + lineNumber * this.fontRenderer.FONT_HEIGHT, PAGE_WIDTH, BLACK); - - List list = new ArrayList( - this.fontRenderer.listFormattedStringToWidth(paragraph, GuiWizardHandbook.PAGE_WIDTH)); - - lineNumber += list.size(); - } - - lineNumber = 0; - - // Prevents crash when the last page is blank (and hence is not in the list of pages) - if(text.size() > pageNumber * 2 + 1){ - for(String paragraph : text.get(pageNumber * 2 + 1)){ - - // First page is centred - if(pageNumber == 0){ - int startX = this.width / 2 + 7 + PAGE_WIDTH / 2 - - this.fontRenderer.getStringWidth(paragraph) / 2; - this.fontRenderer.drawSplitString(paragraph, startX, - yPos + 14 + lineNumber * this.fontRenderer.FONT_HEIGHT, PAGE_WIDTH, BLACK); - }else{ - this.fontRenderer.drawSplitString(paragraph, this.width / 2 + 7, - yPos + 14 + lineNumber * this.fontRenderer.FONT_HEIGHT, PAGE_WIDTH, BLACK); - } - - List list = new ArrayList( - this.fontRenderer.listFormattedStringToWidth(paragraph, GuiWizardHandbook.PAGE_WIDTH)); - - lineNumber += list.size(); - } - } - - // Which page of the recipes this is - int recipePage = pageNumber - (sections.get(sections.size() - 1).pageNumber - 1) / 2; - - if(recipePage >= 0 && recipePage < 4){ - // 4 recipes per page, hence the recipePage*4 - this.renderCraftingRecipe(xPos + 23, yPos + 39, mouseX, mouseY, RECIPES.get(recipePage*4).getRight(), RECIPES.get(recipePage*4).getLeft()); - this.renderCraftingRecipe(xPos + 23, yPos + 98, mouseX, mouseY, RECIPES.get(recipePage*4+1).getRight(), RECIPES.get(recipePage*4+1).getLeft()); - this.renderCraftingRecipe(xPos + 156, yPos + 39, mouseX, mouseY, RECIPES.get(recipePage*4+2).getRight(), RECIPES.get(recipePage*4+2).getLeft()); - this.renderCraftingRecipe(xPos + 156, yPos + 98, mouseX, mouseY, RECIPES.get(recipePage*4+3).getRight(), RECIPES.get(recipePage*4+3).getLeft()); - - // Tooltips are rendered after recipes to prevent tooltips on the left appearing behind items on the right. - this.renderCraftingTooltips(xPos + 23, yPos + 39, mouseX, mouseY, RECIPES.get(recipePage*4).getRight(), RECIPES.get(recipePage*4).getLeft()); - this.renderCraftingTooltips(xPos + 23, yPos + 98, mouseX, mouseY, RECIPES.get(recipePage*4+1).getRight(), RECIPES.get(recipePage*4+1).getLeft()); - this.renderCraftingTooltips(xPos + 156, yPos + 39, mouseX, mouseY, RECIPES.get(recipePage*4+2).getRight(), RECIPES.get(recipePage*4+2).getLeft()); - this.renderCraftingTooltips(xPos + 156, yPos + 98, mouseX, mouseY, RECIPES.get(recipePage*4+3).getRight(), RECIPES.get(recipePage*4+3).getLeft()); - } - - } - - // TODO: In 1.12, this all needs redoing nicely. With the crafting system halfway through changing in 1.11.2, this - // isn't worth doing until then. - - private void renderCraftingRecipe(int xPos, int yPos, int mouseX, int mouseY, NonNullList> craftingGrid, - ItemStack craftingResult){ - - GlStateManager.pushMatrix(); - RenderHelper.enableGUIStandardItemLighting(); - GlStateManager.disableLighting(); - GlStateManager.enableRescaleNormal(); - GlStateManager.enableColorMaterial(); - GlStateManager.enableLighting(); - itemRender.zLevel = 100.0F; - - for(int i = 0; i < craftingGrid.size(); i++){ - for(int j = 0; j < craftingGrid.get(i).size(); j++){ - ItemStack stack = craftingGrid.get(i).get(j); - if(!stack.isEmpty()){ - itemRender.renderItemAndEffectIntoGUI(stack, xPos + 18 * i, yPos + 18 * j); - itemRender.renderItemOverlays(this.fontRenderer, stack, xPos + 18 * i, - yPos + 18 * j); - } - } - } - - if(!craftingResult.isEmpty()){ - itemRender.renderItemAndEffectIntoGUI(craftingResult, xPos + 86, yPos + 18); - itemRender.renderItemOverlays(this.fontRenderer, craftingResult, xPos + 86, yPos + 18); - } - - GlStateManager.popMatrix(); - GlStateManager.enableLighting(); - GlStateManager.enableDepth(); - RenderHelper.enableStandardItemLighting(); - - } - - private void renderCraftingTooltips(int xPos, int yPos, int mouseX, int mouseY, NonNullList> craftingGrid, - ItemStack craftingResult){ - - int guiLeft = this.width / 2 - xSize / 2; - int guiTop = this.height / 2 - this.ySize / 2; - - GlStateManager.pushMatrix(); - RenderHelper.enableGUIStandardItemLighting(); - GlStateManager.disableLighting(); - GlStateManager.enableRescaleNormal(); - GlStateManager.enableColorMaterial(); - itemRender.zLevel = 0.0F; - GlStateManager.disableLighting(); - - for(int i = 0; i < craftingGrid.size(); i++){ - for(int j = 0; j < craftingGrid.get(i).size(); j++){ - ItemStack stack = craftingGrid.get(i).get(j); - if(!stack.isEmpty() - && isPointInRegion(xPos + 18 * i, yPos + 18 * j, 16, 16, mouseX + guiLeft, mouseY + guiTop)){ - this.renderToolTip(stack, mouseX, mouseY); - } - } - } - - if(!craftingResult.isEmpty() && isPointInRegion(xPos + 86, yPos + 18, 16, 16, mouseX + guiLeft, mouseY + guiTop)){ - this.renderToolTip(craftingResult, mouseX, mouseY); - } - - GlStateManager.popMatrix(); - GlStateManager.enableLighting(); - GlStateManager.enableDepth(); - RenderHelper.enableStandardItemLighting(); - - } - - @Override - public void initGui(){ - - super.initGui(); - Keyboard.enableRepeatEvents(true); - - int nextButtonId = 0; - - this.buttonList.clear(); - this.buttonList.add(new GuiButtonTurnPage(nextButtonId++, this.width / 2 + this.xSize / 2 - 22 - 23, - this.height / 2 + this.ySize / 2 - 10 - 13, true)); - this.buttonList.add(new GuiButtonTurnPage(nextButtonId++, this.width / 2 - this.xSize / 2 + 21, - this.height / 2 + this.ySize / 2 - 10 - 13, false)); - - text = new ArrayList>(1); - sections = new ArrayList
(1); - - BufferedReader bufferedreader = null; - - String textFilepath = Wizardry.MODID + ":texts/handbook_" - + Minecraft.getMinecraft().getLanguageManager().getCurrentLanguage().getLanguageCode() + ".txt"; - - try{ - - bufferedreader = new BufferedReader(new InputStreamReader( - this.mc.getResourceManager().getResource(new ResourceLocation(textFilepath)).getInputStream(), - StandardCharsets.UTF_8)); - - }catch (IOException e){ - - Wizardry.logger.info( - "Wizard handbook text file missing for the current language. Using default (English - US) instead."); - - textFilepath = Wizardry.MODID + ":texts/handbook_en_us.txt"; - - try { - - bufferedreader = new BufferedReader(new InputStreamReader( - this.mc.getResourceManager().getResource(new ResourceLocation(textFilepath)).getInputStream(), - StandardCharsets.UTF_8)); - - } catch (IOException x){ - Wizardry.logger.error("Couldn't find file: " + Wizardry.MODID + "/assets/texts/handbook_en_us.txt. The file may be" - + "missing; please try re-downloading and reinstalling Wizardry.", x); - } - } - - if(bufferedreader != null){ - - try{ - - String paragraph = bufferedreader.readLine(); - ArrayList page = new ArrayList(1); - - int linesPerPage = 16; - - int lineNumber = 0; - - while(paragraph != null){ - - // System.out.println(paragraph); - - if(paragraph.contains("PAGEBREAK") || lineNumber >= linesPerPage){ - - text.add(page); - - page = new ArrayList(1); - - lineNumber = 0; - - if(paragraph.contains("PAGEBREAK")) paragraph = bufferedreader.readLine(); - - }else if(paragraph.contains("LINEBREAK")){ - - lineNumber++; - - page.add(""); - - paragraph = bufferedreader.readLine(); - - }else if(paragraph.contains("SECTION")){ - - sections.add( - new Section(paragraph.replace("SECTION ", ""), text.size() + 1, this.width / 2 + 7, - this.height / 2 - this.ySize / 2 + 14 - + (sections.size() + 2) * this.fontRenderer.FONT_HEIGHT, - nextButtonId++)); - paragraph = bufferedreader.readLine(); - - }else if(paragraph.contains("IMAGE")){ - - if(paragraph.contains("WORKBENCH")){ - this.guiPage = text.size() + 1; - }else if(paragraph.contains("CRYSTAL")){ - this.imagePage = text.size() + 1; - } - - paragraph = bufferedreader.readLine(); - - }else{ - - paragraph = paragraph.replaceAll("NEXT_SPELL_KEY", ClientProxy.NEXT_SPELL.getDisplayName()); - paragraph = paragraph.replaceAll("PREVIOUS_SPELL_KEY", ClientProxy.PREVIOUS_SPELL.getDisplayName()); - paragraph = paragraph.replaceAll("MANA_PER_CRYSTAL_MINUS_30", "" + (Constants.MANA_PER_CRYSTAL - 30)); - paragraph = paragraph.replaceAll("MANA_PER_CRYSTAL", "" + Constants.MANA_PER_CRYSTAL); - paragraph = paragraph.replaceAll("BASIC_MAX_CHARGE", "" + Tier.BASIC.maxCharge); - paragraph = paragraph.replaceAll("APPRENTICE_MAX_CHARGE", "" + Tier.APPRENTICE.maxCharge); - paragraph = paragraph.replaceAll("ADVANCED_MAX_CHARGE", "" + Tier.ADVANCED.maxCharge); - paragraph = paragraph.replaceAll("MASTER_MAX_CHARGE", "" + Tier.MASTER.maxCharge); - paragraph = paragraph.replaceAll("BASIC_COLOUR", "\u00A77"); - paragraph = paragraph.replaceAll("APPRENTICE_COLOUR", Tier.APPRENTICE.getFormattingCode()); - paragraph = paragraph.replaceAll("ADVANCED_COLOUR", Tier.ADVANCED.getFormattingCode()); - paragraph = paragraph.replaceAll("MASTER_COLOUR", Tier.MASTER.getFormattingCode()); - paragraph = paragraph.replaceAll("FIRE_COLOUR", Element.FIRE.getFormattingCode()); - paragraph = paragraph.replaceAll("ICE_COLOUR", Element.ICE.getFormattingCode()); - paragraph = paragraph.replaceAll("LIGHTNING_COLOUR", Element.LIGHTNING.getFormattingCode()); - paragraph = paragraph.replaceAll("NECROMANCY_COLOUR", Element.NECROMANCY.getFormattingCode()); - paragraph = paragraph.replaceAll("EARTH_COLOUR", Element.EARTH.getFormattingCode()); - paragraph = paragraph.replaceAll("SORCERY_COLOUR", Element.SORCERY.getFormattingCode()); - paragraph = paragraph.replaceAll("HEALING_COLOUR", Element.HEALING.getFormattingCode()); - paragraph = paragraph.replaceAll("RESET_COLOUR", "\u00A70"); - paragraph = paragraph.replaceAll("MCVERSION", "1.12.2"); - paragraph = paragraph.replaceAll("VERSION", Wizardry.VERSION); - - int linesInParagraph = this.fontRenderer - .listFormattedStringToWidth(paragraph, GuiWizardHandbook.PAGE_WIDTH).size(); - - // Ignores empty lines at the top of a page. - if(paragraph.isEmpty() && lineNumber == 0){ - - paragraph = bufferedreader.readLine(); - - // Normal paragraph, all on one page - }else if(lineNumber + linesInParagraph <= linesPerPage){ - - page.add(paragraph); - - lineNumber += linesInParagraph; - - paragraph = bufferedreader.readLine(); - - // Paragraphs split across two pages (or more?) - }else{ - - int linesInFirstPart = linesPerPage - lineNumber; - - String paragraphFirstPart = ""; - String paragraphLastPart = ""; - - int i = 0; - - List strings = this.fontRenderer.listFormattedStringToWidth(paragraph, - GuiWizardHandbook.PAGE_WIDTH); - - for(Object s : strings){ - if(i < linesInFirstPart){ - paragraphFirstPart = paragraphFirstPart.concat((String)s + " "); - }else{ - paragraphLastPart = paragraphLastPart.concat((String)s + " "); - } - i++; - } - - // System.out.println("Paragraph crosses page boundary; string split into: \"" + - // paragraphFirstPart + "\" and \"" + paragraphLastPart + "\""); - - page.add(paragraphFirstPart); - - lineNumber += linesInFirstPart; - - paragraph = paragraphLastPart; - } - } - } - - text.add(page); - - }catch (IOException e){ - Wizardry.logger.error("Something went wrong reading file: " + textFilepath - + ". The file may be damaged; please try re-downloading and reinstalling wizardry.", e); - } - } - } - - private class Section { - - /** The integer text colour used for the section when it is moused over. Currently orange. */ - private static final int HIGHLIGHT_COLOUR = 0xdd4c1d; - - String name; - int pageNumber; - int x, y; - int buttonId; - - Section(String name, int pageNumber, int x, int y, int id){ - this.name = name; - this.pageNumber = pageNumber; - this.x = x; - this.y = y; - this.buttonId = id; - GuiWizardHandbook.this.buttonList.add(new GuiButtonInvisible(id, x, y, GuiWizardHandbook.PAGE_WIDTH, - GuiWizardHandbook.this.fontRenderer.FONT_HEIGHT)); - } - - void hideButton(){ - GuiWizardHandbook.this.buttonList.get(buttonId).visible = false; - } - - void drawContents(){ - - GuiWizardHandbook.this.buttonList.get(buttonId).visible = true; - - GuiWizardHandbook.this.fontRenderer.drawString(name, x, y, - GuiWizardHandbook.this.buttonList.get(buttonId).isMouseOver() ? HIGHLIGHT_COLOUR : BLACK); - - int nameWidth = GuiWizardHandbook.this.fontRenderer.getStringWidth(name); - - String dotsAndNumber = " " + this.pageNumber; - - while(GuiWizardHandbook.this.fontRenderer.getStringWidth(dotsAndNumber) < GuiWizardHandbook.PAGE_WIDTH - - nameWidth - 2){ - dotsAndNumber = "." + dotsAndNumber; - } - - GuiWizardHandbook.this.fontRenderer.drawString(dotsAndNumber, x + GuiWizardHandbook.PAGE_WIDTH - - GuiWizardHandbook.this.fontRenderer.getStringWidth(dotsAndNumber), y, BLACK); - } - } - - @Override - public void onGuiClosed(){ - super.onGuiClosed(); - Keyboard.enableRepeatEvents(false); - } - - /** - * Fired when a control is clicked. This is the equivalent of ActionListener.actionPerformed(ActionEvent e). - */ - @Override - protected void actionPerformed(GuiButton par1GuiButton){ - - if(par1GuiButton.enabled){ - if(par1GuiButton.id == 0){ - if(pageNumber < (text.size() - 1) / 2) pageNumber++; - }else if(par1GuiButton.id == 1){ - if(pageNumber > 0) pageNumber--; - }else{ - if(pageNumber == 1) pageNumber = (sections.get(par1GuiButton.id - 2).pageNumber - 1) / 2; - } - } - } - - /** - * Args: left, top, width, height, pointX, pointY. Note: left, top are local to Gui, pointX, pointY are local to - * screen - */ - protected boolean isPointInRegion(int par1, int par2, int par3, int par4, int par5, int par6){ - int k1 = this.width / 2 - xSize / 2; - int l1 = this.height / 2 - this.ySize / 2; - par5 -= k1; - par6 -= l1; - return par5 >= par1 - 1 && par5 < par1 + par3 + 1 && par6 >= par2 - 1 && par6 < par2 + par4 + 1; - } - - /** - * Draws a textured rectangle, stretching the section of the image to fit the size given. - * - * @param x The x position of the rectangle - * @param y The y position of the rectangle - * @param u The x position of the top left corner of the section of the image wanted, expressed as a fraction of the - * image width - * @param v The y position of the top left corner of the section of the image wanted, expressed as a fraction of the - * image width - * @param finalWidth The width as rendered - * @param finalHeight The height as rendered - * @param width The width of the section, expressed as a fraction of the image width - * @param height The height of the section, expressed as a fraction of the image width - */ - public static void drawTexturedStretchedRect(int x, int y, int u, int v, int finalWidth, int finalHeight, int width, - int height){ - - Tessellator tessellator = Tessellator.getInstance(); - BufferBuilder buffer = tessellator.getBuffer(); - buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); - buffer.pos((x), y + finalHeight, 0).tex(u, v + height).endVertex(); - buffer.pos(x + finalWidth, y + finalHeight, 0).tex(u + width, v + height).endVertex(); - buffer.pos(x + finalWidth, (y), 0).tex(u + width, v).endVertex(); - buffer.pos((x), (y), 0).tex(u, v).endVertex(); - tessellator.draw(); - } - - private static NonNullList> createGrid(){ - NonNullList> grid = NonNullList.withSize(3, NonNullList.create()); - for(int i=0; i<3; i++){ - grid.set(i, NonNullList.withSize(3, ItemStack.EMPTY)); - } - return grid; - } - - /** Called from init() in the main mod class to initialise the recipes for display in the handbook. */ - public static void initDisplayRecipes(){ - - NonNullList> craftingGrid; - ItemStack craftingResult; - - craftingGrid = createGrid(); - craftingGrid.get(0).set(0, new ItemStack(Items.GOLD_NUGGET)); - craftingGrid.get(1).set(0, new ItemStack(Blocks.CARPET, 1, 10)); - craftingGrid.get(2).set(0, new ItemStack(Items.GOLD_NUGGET)); - craftingGrid.get(0).set(1, new ItemStack(WizardryItems.magic_crystal)); - craftingGrid.get(1).set(1, new ItemStack(Blocks.LAPIS_BLOCK)); - craftingGrid.get(2).set(1, new ItemStack(WizardryItems.magic_crystal)); - craftingGrid.get(0).set(2, new ItemStack(Blocks.STONE)); - craftingGrid.get(1).set(2, new ItemStack(Blocks.STONE)); - craftingGrid.get(2).set(2, new ItemStack(Blocks.STONE)); - craftingResult = new ItemStack(WizardryBlocks.arcane_workbench); - RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid)); - - craftingGrid = createGrid(); - craftingGrid.get(2).set(0, new ItemStack(WizardryItems.magic_crystal)); - craftingGrid.get(1).set(1, new ItemStack(Items.STICK)); - craftingGrid.get(0).set(2, new ItemStack(Items.GOLD_NUGGET)); - craftingResult = new ItemStack(WizardryItems.magic_wand); - RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid)); - - craftingGrid = createGrid(); - craftingGrid.get(1).set(0, new ItemStack(WizardryItems.magic_crystal)); - craftingGrid.get(0).set(1, new ItemStack(WizardryItems.magic_crystal)); - craftingGrid.get(1).set(1, new ItemStack(Items.BOOK)); - craftingGrid.get(1).set(2, new ItemStack(WizardryItems.magic_crystal)); - craftingGrid.get(2).set(1, new ItemStack(WizardryItems.magic_crystal)); - craftingResult = new ItemStack(WizardryItems.spell_book, 1, 1); - RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid)); - - craftingGrid = createGrid(); - craftingGrid.get(0).set(0, new ItemStack(Items.BOOK)); - craftingGrid.get(1).set(0, new ItemStack(WizardryItems.magic_crystal)); - craftingResult = new ItemStack(WizardryItems.wizard_handbook); - RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid)); - - craftingGrid = createGrid(); - craftingGrid.get(0).set(0, new ItemStack(WizardryBlocks.crystal_flower)); - craftingResult = new ItemStack(WizardryItems.magic_crystal, 2); - RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid)); - - craftingGrid = createGrid(); - craftingGrid.get(0).set(0, new ItemStack(WizardryItems.magic_crystal)); - craftingGrid.get(1).set(0, new ItemStack(WizardryItems.magic_crystal)); - craftingGrid.get(2).set(0, new ItemStack(WizardryItems.magic_crystal)); - craftingGrid.get(0).set(1, new ItemStack(WizardryItems.magic_crystal)); - craftingGrid.get(1).set(1, new ItemStack(Items.GLASS_BOTTLE)); - craftingGrid.get(2).set(1, new ItemStack(WizardryItems.magic_crystal)); - craftingGrid.get(0).set(2, new ItemStack(WizardryItems.magic_crystal)); - craftingGrid.get(1).set(2, new ItemStack(WizardryItems.magic_crystal)); - craftingGrid.get(2).set(2, new ItemStack(WizardryItems.magic_crystal)); - craftingResult = new ItemStack(WizardryItems.mana_flask); - RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid)); - - craftingGrid = createGrid(); - craftingGrid.get(1).set(0, new ItemStack(Blocks.STONE)); - craftingGrid.get(0).set(1, new ItemStack(Blocks.STONE)); - craftingGrid.get(1).set(1, new ItemStack(WizardryItems.magic_crystal)); - craftingGrid.get(1).set(2, new ItemStack(Blocks.STONE)); - craftingGrid.get(2).set(1, new ItemStack(Blocks.STONE)); - craftingResult = new ItemStack(WizardryBlocks.transportation_stone, 2); - RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid)); - - craftingGrid = createGrid(); - craftingGrid.get(1).set(0, new ItemStack(Items.STRING)); - craftingGrid.get(0).set(1, new ItemStack(Items.STRING)); - craftingGrid.get(1).set(1, new ItemStack(WizardryItems.magic_crystal)); - craftingGrid.get(1).set(2, new ItemStack(Items.STRING)); - craftingGrid.get(2).set(1, new ItemStack(Items.STRING)); - craftingResult = new ItemStack(WizardryItems.magic_silk, 2); - RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid)); - - craftingGrid = createGrid(); - craftingGrid.get(0).set(0, new ItemStack(WizardryItems.magic_silk)); - craftingGrid.get(1).set(0, new ItemStack(WizardryItems.magic_silk)); - craftingGrid.get(2).set(0, new ItemStack(WizardryItems.magic_silk)); - craftingGrid.get(0).set(1, new ItemStack(WizardryItems.magic_silk)); - craftingGrid.get(2).set(1, new ItemStack(WizardryItems.magic_silk)); - craftingResult = new ItemStack(WizardryItems.wizard_hat); - RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid)); - - craftingGrid = createGrid(); - craftingGrid.get(0).set(0, new ItemStack(WizardryItems.magic_silk)); - craftingGrid.get(2).set(0, new ItemStack(WizardryItems.magic_silk)); - craftingGrid.get(0).set(1, new ItemStack(WizardryItems.magic_silk)); - craftingGrid.get(1).set(1, new ItemStack(WizardryItems.magic_silk)); - craftingGrid.get(2).set(1, new ItemStack(WizardryItems.magic_silk)); - craftingGrid.get(0).set(2, new ItemStack(WizardryItems.magic_silk)); - craftingGrid.get(1).set(2, new ItemStack(WizardryItems.magic_silk)); - craftingGrid.get(2).set(2, new ItemStack(WizardryItems.magic_silk)); - craftingResult = new ItemStack(WizardryItems.wizard_robe); - RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid)); - - craftingGrid = createGrid(); - craftingGrid.get(0).set(0, new ItemStack(WizardryItems.magic_silk)); - craftingGrid.get(1).set(0, new ItemStack(WizardryItems.magic_silk)); - craftingGrid.get(2).set(0, new ItemStack(WizardryItems.magic_silk)); - craftingGrid.get(0).set(1, new ItemStack(WizardryItems.magic_silk)); - craftingGrid.get(2).set(1, new ItemStack(WizardryItems.magic_silk)); - craftingGrid.get(0).set(2, new ItemStack(WizardryItems.magic_silk)); - craftingGrid.get(2).set(2, new ItemStack(WizardryItems.magic_silk)); - craftingResult = new ItemStack(WizardryItems.wizard_leggings); - RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid)); - - craftingGrid = createGrid(); - craftingGrid.get(0).set(0, new ItemStack(WizardryItems.magic_silk)); - craftingGrid.get(2).set(0, new ItemStack(WizardryItems.magic_silk)); - craftingGrid.get(0).set(1, new ItemStack(WizardryItems.magic_silk)); - craftingGrid.get(2).set(1, new ItemStack(WizardryItems.magic_silk)); - craftingResult = new ItemStack(WizardryItems.wizard_boots); - RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid)); - - craftingGrid = createGrid(); - craftingGrid.get(0).set(0, new ItemStack(Items.PAPER)); - craftingGrid.get(1).set(0, new ItemStack(Items.STRING)); - craftingResult = new ItemStack(WizardryItems.blank_scroll); - RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid)); - - craftingGrid = createGrid(); - craftingGrid.get(0).set(0, new ItemStack(Items.BLAZE_POWDER)); - craftingGrid.get(1).set(0, new ItemStack(Items.BLAZE_POWDER)); - craftingGrid.get(0).set(1, new ItemStack(Items.GLASS_BOTTLE)); - craftingGrid.get(1).set(1, new ItemStack(Items.GUNPOWDER)); - craftingResult = new ItemStack(WizardryItems.firebomb, 3); - RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid)); - - craftingGrid = createGrid(); - craftingGrid.get(0).set(0, new ItemStack(Items.SPIDER_EYE)); - craftingGrid.get(1).set(0, new ItemStack(Items.SPIDER_EYE)); - craftingGrid.get(0).set(1, new ItemStack(Items.GLASS_BOTTLE)); - craftingGrid.get(1).set(1, new ItemStack(Items.GUNPOWDER)); - craftingResult = new ItemStack(WizardryItems.poison_bomb, 3); - RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid)); - - craftingGrid = createGrid(); - craftingGrid.get(0).set(0, new ItemStack(Items.COAL)); - craftingGrid.get(1).set(0, new ItemStack(Items.COAL)); - craftingGrid.get(0).set(1, new ItemStack(Items.GLASS_BOTTLE)); - craftingGrid.get(1).set(1, new ItemStack(Items.GUNPOWDER)); - craftingResult = new ItemStack(WizardryItems.smoke_bomb, 3); - RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid)); - } - -} \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/client/gui/handbook/Contents.java b/src/main/java/electroblob/wizardry/client/gui/handbook/Contents.java new file mode 100644 index 00000000..2ef67821 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/gui/handbook/Contents.java @@ -0,0 +1,181 @@ +package electroblob.wizardry.client.gui.handbook; + +import com.google.gson.JsonObject; +import com.google.gson.JsonSyntaxException; +import electroblob.wizardry.client.DrawingUtils; +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.util.JsonUtils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * Instances of this class represent tables of contents in the wizard's handbook. Each {@link Section} can have a + * single table of contents, which can reference any other sections in the handbook (though it is normal to list + * top-level sections in a main contents and have subsections listed in their respective parent sections' contents). + * + * This class handles JSON parsing, formatting and drawing of the contents itself, working on a line-by-line basis + * (as opposed to sections, which work on a page-by-page basis). It also stores its own list of buttons. + * + * @author Electroblob + * @since Wizardry 4.2 + */ +class Contents { + + // Final fields are mandatory, the rest are optional + final String id; + private boolean hyperlinks = true; + private boolean pageNumbers = true; + private String separator = "."; + // Derived fields, not specifically defined in JSON + private int startPage; + private int startLine; + private final List> buttons; + + private final List
entries; + + private Contents(String id){ + this.id = id; + this.entries = new ArrayList<>(); + this.buttons = new ArrayList<>(); + } + + /** Returns an unmodifiable, flattened collection of all the buttons in this contents. */ + Collection getButtons(){ + return WizardryUtilities.flatten(buttons); + } + + void addEntry(Section section){ + entries.add(section); + } + + /** + * Draws this contents for the given double-page spread and shows/hides buttons accordingly. Will draw nothing + * if the given page is outside of this contents. + * + * @param font The font renderer object. + * @param doublePage The index of the double-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, int doublePage, int left, int top){ + + // Show/hide buttons + + int i = 0; + + for(List list : buttons){ + final int i1 = i++; + list.forEach(b -> b.visible = GuiWizardHandbook.singleToDoublePage(startPage + i1) == doublePage); + } + + if(!pageNumbers) return; // No page numbers means only the buttons are drawn + + // FONT_HEIGHT may change between fonts, so this is calculated here. With the default font it's 14. + final int maxLineNumber = GuiWizardHandbook.PAGE_HEIGHT / font.FONT_HEIGHT; + + int leftIndex = GuiWizardHandbook.doubleToSinglePage(doublePage, false); + // Relative indices of the pages to be rendered - often these will be outside the section entirely + int[] visiblePages = {leftIndex - startPage, leftIndex - startPage + 1}; + + for(int page : visiblePages){ + + if(page >= 0 && page < entries.size() / maxLineNumber + 1){ + + int x = left + (GuiWizardHandbook.isRightPage(startPage + page) ? GuiWizardHandbook.GUI_WIDTH - GuiWizardHandbook.TEXT_INSET_X - GuiWizardHandbook.PAGE_WIDTH : GuiWizardHandbook.TEXT_INSET_X); + int y = top + GuiWizardHandbook.TEXT_INSET_Y + startLine * font.FONT_HEIGHT; + + for(Section entry : this.entries){ + + int nameWidth = font.getStringWidth(entry.title); + + String dotsAndNumber = " " + entry.startPage; + + while(font.getStringWidth(dotsAndNumber) < GuiWizardHandbook.PAGE_WIDTH - nameWidth - 2){ + dotsAndNumber = separator + dotsAndNumber; + } + + font.drawString(dotsAndNumber, x + GuiWizardHandbook.PAGE_WIDTH - font.getStringWidth(dotsAndNumber), y, DrawingUtils.BLACK, false); + + if(!hyperlinks) font.drawString(entry.title, x, y, DrawingUtils.BLACK, false); + + y += font.FONT_HEIGHT; + } + } + } + } + + /** + * Called on GUI load to format the section and all subsections, contents tables and other elements. Does not + * perform any actual drawing. + * + * @param font The font renderer object, for measurement purposes. + * @param startPage The index of the first page (single side, not double-page) of this section. + * @param startLine The index of the first line of this contents. + * @param left The x coordinate of the left side of the GUI. + * @param top The y coordinate of the top of the GUI. + * @return The number of lines this contents takes up. + * @throws JsonSyntaxException if at any point the formatting is found to be invalid. + */ + int format(FontRenderer font, int startPage, int startLine, int left, int top){ + + this.buttons.clear(); + + if(hyperlinks){ + + // FONT_HEIGHT may change between fonts, so this is calculated here. With the default font it's 14. + final int maxLineNumber = GuiWizardHandbook.PAGE_HEIGHT / font.FONT_HEIGHT; + + this.startPage = startPage; + this.startLine = startLine; + + List list = new ArrayList<>(maxLineNumber); + + for(Section entry : this.entries){ + + int x = GuiWizardHandbook.isRightPage(startPage) ? left + GuiWizardHandbook.GUI_WIDTH - GuiWizardHandbook.TEXT_INSET_X - GuiWizardHandbook.PAGE_WIDTH : left + GuiWizardHandbook.TEXT_INSET_X; + int y = top + GuiWizardHandbook.TEXT_INSET_Y + startLine * font.FONT_HEIGHT; + + list.add(new GuiButtonHyperlink.Internal(0, x, y, font, entry.title, entry, 0, "")); + + startLine++; + + if(startLine == maxLineNumber){ + startLine = 0; + startPage++; + buttons.add(list); + list = new ArrayList<>(maxLineNumber); // If there are no more entries this will be discarded anyway + } + } + + buttons.add(list); + } + + // Returning this is kind of trivial at the moment but if we ever wanted to add a header or something, + // it would be more useful. + return entries.size(); + } + + /** + * Parses the given JSON object and constructs a new {@code Contents} from it, setting all the relevant fields + * and references. + * + * @param json A JSON object representing the contents to be constructed. This must contain at least an "id" + * string. + * @return The resulting {@code Contents} object. + * @throws JsonSyntaxException if at any point the JSON object is found to be invalid. + */ + static Contents fromJson(JsonObject json){ + + Contents contents = new Contents(JsonUtils.getString(json, "id")); + + contents.hyperlinks = JsonUtils.getBoolean(json, "hyperlinks", true); + contents.pageNumbers = JsonUtils.getBoolean(json, "page_numbers", true); + contents.separator = JsonUtils.getString(json, "separator", "."); + + return contents; + } +} diff --git a/src/main/java/electroblob/wizardry/client/gui/handbook/CraftingRecipe.java b/src/main/java/electroblob/wizardry/client/gui/handbook/CraftingRecipe.java new file mode 100644 index 00000000..ecaf5105 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/gui/handbook/CraftingRecipe.java @@ -0,0 +1,225 @@ +package electroblob.wizardry.client.gui.handbook; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonSyntaxException; +import electroblob.wizardry.client.DrawingUtils; +import electroblob.wizardry.spell.Mine; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.gui.recipebook.GuiRecipeBook; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.RenderHelper; +import net.minecraft.client.renderer.RenderItem; +import net.minecraft.item.Item; +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.stats.RecipeBook; +import net.minecraft.util.JsonUtils; +import net.minecraft.util.NonNullList; +import net.minecraft.util.ResourceLocation; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +class CraftingRecipe { + + static final int WIDTH = 111, HEIGHT = 56; + + // Final fields are mandatory, the rest are optional + private final ResourceLocation location; + // Derived fields, not specifically defined in JSON + private IRecipe recipe; + private final Set instances = new HashSet<>(); + + private CraftingRecipe(ResourceLocation location){ + this.location = location; + } + + /** + * 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(){ + + this.recipe = CraftingManager.getRecipe(location); + + if(recipe == null) throw new JsonSyntaxException("No such recipe: " + location); + } + + /** + * 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){ + for(int[] instance : instances){ + if(GuiWizardHandbook.singleToDoublePage(instance[0]) == doublePage){ + renderCraftingRecipe(font, itemRenderer, left + instance[1], top + instance[2], this.recipe); + } + } + } + + /** + * 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){ + for(int[] instance : instances){ + if(GuiWizardHandbook.singleToDoublePage(instance[0]) == doublePage){ + renderCraftingTooltips(gui, itemRenderer, left + instance[1], top + instance[2], mouseX, mouseY, this.recipe); + } + } + } + + /** + * 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 "location" + * 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 location = new ResourceLocation(JsonUtils.getString(json, "location")); + return new CraftingRecipe(location); + } + + 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, IRecipe recipe){ + + ItemStack result = recipe.getRecipeOutput(); + + GlStateManager.color(1, 1, 1, 1); + Minecraft.getMinecraft().renderEngine.bindTexture(GuiWizardHandbook.texture); + + DrawingUtils.drawTexturedRect(x - 2, y - 2, 60, 190, WIDTH, HEIGHT, 512, 256); + + GlStateManager.pushMatrix(); + RenderHelper.enableGUIStandardItemLighting(); + GlStateManager.disableLighting(); + GlStateManager.enableRescaleNormal(); + GlStateManager.enableColorMaterial(); + GlStateManager.enableLighting(); + itemRenderer.zLevel = 100.0F; + + // TODO: There may be a better way of doing this + 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 + 18 * (i%3), y + 18 * (i/3)); + itemRenderer.renderItemOverlays(font, stack, x + 18 * (i%3), y + 18 * (i/3)); + } + } + + i++; + } + + if(!result.isEmpty()){ + itemRenderer.renderItemAndEffectIntoGUI(result, x + 86, y + 18); + itemRenderer.renderItemOverlays(font, result, x + 86, y + 18); + } + + GlStateManager.popMatrix(); + GlStateManager.enableLighting(); + GlStateManager.enableDepth(); + GlStateManager.disableColorMaterial(); + itemRenderer.zLevel = 0.0F; + //RenderHelper.enableStandardItemLighting(); + + } + + 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; + GlStateManager.disableLighting(); + + // TODO: There may be a better way of doing this + 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 + 18 * (i%3), y + 18 * (i/3), 16, 16, mouseX, mouseY)){ + gui.renderToolTip(stack, mouseX, mouseY); + } + } + + i++; + } + + if(!result.isEmpty() && isPointInRegion(x + 86, y + 18, 16, 16, mouseX, mouseY)){ + gui.renderToolTip(result, mouseX, mouseY); + } + + GlStateManager.popMatrix(); + GlStateManager.enableLighting(); + GlStateManager.enableDepth(); + GlStateManager.disableColorMaterial(); + RenderHelper.enableStandardItemLighting(); + + } + + 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; + } + +} diff --git a/src/main/java/electroblob/wizardry/client/gui/handbook/GuiButtonHyperlink.java b/src/main/java/electroblob/wizardry/client/gui/handbook/GuiButtonHyperlink.java new file mode 100644 index 00000000..7aaa5bf4 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/gui/handbook/GuiButtonHyperlink.java @@ -0,0 +1,179 @@ +package electroblob.wizardry.client.gui.handbook; + +import com.google.gson.JsonSyntaxException; +import electroblob.wizardry.registry.WizardrySounds; +import net.minecraft.client.Minecraft; +import net.minecraft.client.audio.PositionedSoundRecord; +import net.minecraft.client.audio.SoundHandler; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.TextComponentString; +import net.minecraft.util.text.TextFormatting; +import net.minecraft.util.text.event.ClickEvent; + +import java.util.ArrayList; +import java.util.List; + +public abstract class GuiButtonHyperlink extends GuiButton { + + public static final String URL_REGEX = "^((https?|ftp)://|(www|ftp)\\.)?[a-z0-9-]+(\\.[a-z0-9-]+)+([/?].*)?$"; + + final int indent; + final List lines; + + GuiButtonHyperlink(int id, int x, int y, FontRenderer font, String text, int indent, String suffix){ + + super(id, x, y, font.getStringWidth(text), font.FONT_HEIGHT, text); + + // Sometimes a link has punctuation or something after it that causes it to wrap onto a new line + String linkWithSuffix = text + suffix; + + // If the string won't fit any words at the end of the current line, treat it as if we started a new line + if(font.getStringWidth(linkWithSuffix.split("\\s")[0]) > GuiWizardHandbook.PAGE_WIDTH - indent){ + indent = 0; + this.y += font.FONT_HEIGHT; + } + + this.indent = indent; // Assigned here in case it was corrected above + + String line1 = font.listFormattedStringToWidth(linkWithSuffix, GuiWizardHandbook.PAGE_WIDTH - indent).get(0); + // Without trim(), there will be at least 1 leading space due to the custom wrapping + String remainder = linkWithSuffix.substring(line1.length()).trim(); + + // ... then wrap the rest to the normal width. + lines = new ArrayList<>(); + lines.add(line1); + // Some links are only one line, if this wasn't checked they would cause a StackOverflowError + if(!remainder.isEmpty()) lines.addAll(font.listFormattedStringToWidth(remainder, GuiWizardHandbook.PAGE_WIDTH)); + + // Removes the suffix if it exists (ugly as heck, but it works) + if(!suffix.isEmpty()){ + for(int i=lines.size()-1; i>=0; i--){ + String line = lines.get(i); + if(suffix.endsWith(line)){ + lines.remove(i); + }else if(line.endsWith(suffix)){ + lines.set(i, line.substring(0, line.length() - suffix.length())); + break; + } + } + } + } + + public boolean isHovered(net.minecraft.client.gui.FontRenderer font, int mouseX, int mouseY){ + + int i = 0; + + for(String line : lines){ + + int l = x; + if(i == 0) l += indent; + + int t = y + font.FONT_HEIGHT * i; + + if(mouseX >= l && mouseY >= t && mouseX < l + font.getStringWidth(line) && mouseY < t + font.FONT_HEIGHT){ + return true; + } + + i++; + } + + return false; + } + + @Override + public boolean mousePressed(Minecraft minecraft, int mouseX, int mouseY){ + return this.enabled && this.visible && isHovered(minecraft.fontRenderer, mouseX, mouseY); + } + + @Override + public void drawButton(Minecraft minecraft, int mouseX, int mouseY, float partialTicks){ + + if(this.visible){ + + this.hovered = isHovered(minecraft.fontRenderer, mouseX, mouseY); + int colour = hovered ? GuiWizardHandbook.colours.get("highlight") : GuiWizardHandbook.colours.get("hyperlink"); + + int i = 0; + + for(String line : lines){ + + int l = x; + if(i == 0) l += indent; + + int t = y + minecraft.fontRenderer.FONT_HEIGHT * i; + + minecraft.fontRenderer.drawString(line, l, t, colour); + + i++; + } + } + } + + /** + * Creates a new hyperlink button from the given arguments, automatically differentiating between URLs and sections. + * @param x The x position of the button + * @param y The y position of the button + * @param font A reference to the FontRenderer object + * @param upToLink The paragraph (as a list of lines) up to the link, used to determine positioning and word wrap + * @param arguments The link arguments - that is, everything between the two @ signs, split by spaces + * @param suffix The text directly after the link, up to the first whitespace; used for word wrap. Usually this is + * either empty or contains a single punctuation mark. + * @return The resulting button + * @throws IllegalArgumentException if the given argument array is empty or contains more than 2 arguments + * @throws JsonSyntaxException if the specified link target is not a URL or a valid section ID + */ + public static GuiButtonHyperlink create(int x, int y, FontRenderer font, List upToLink, String[] arguments, String suffix){ + + if(arguments.length == 0 || arguments.length > 2) throw new IllegalArgumentException("Incorrect array length!"); + + GuiButtonHyperlink button; + + if(arguments[0].matches(URL_REGEX)){ + + button = new GuiButtonHyperlink.External(0, x, y, font, arguments[arguments.length - 1], arguments[0], + font.getStringWidth(upToLink.get(upToLink.size() - 1)), suffix); + + }else{ + + Section target = GuiWizardHandbook.sections.get(arguments[0]); + + if(target == null) throw new JsonSyntaxException("Hyperlink points to nonexistent section id " + arguments[0]); + + button = new GuiButtonHyperlink.Internal(0, x, y, font, arguments[arguments.length - 1], + target, font.getStringWidth(upToLink.get(upToLink.size() - 1)), suffix); + } + + return button; + } + + static class Internal extends GuiButtonHyperlink { + + final Section target; + + Internal(int id, int x, int y, FontRenderer font, String text, Section target, int indent, String suffix){ + super(id, x, y, font, text, indent, suffix); + this.target = target; + } + + @Override + public void playPressSound(SoundHandler soundHandler){ + soundHandler.playSound(PositionedSoundRecord.getMasterRecord(WizardrySounds.MISC_PAGE_TURN, 1)); + } + + } + + static class External extends GuiButtonHyperlink { + + final ITextComponent link; + + External(int id, int x, int y, FontRenderer font, String text, String url, int indent, String suffix){ + super(id, x, y, font, text, indent, suffix); + this.link = new TextComponentString(text); + link.getStyle().setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, url)); + } + + } + +} diff --git a/src/main/java/electroblob/wizardry/client/gui/handbook/GuiButtonTurnPage.java b/src/main/java/electroblob/wizardry/client/gui/handbook/GuiButtonTurnPage.java new file mode 100644 index 00000000..a05ed041 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/gui/handbook/GuiButtonTurnPage.java @@ -0,0 +1,63 @@ +package electroblob.wizardry.client.gui.handbook; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.client.DrawingUtils; +import electroblob.wizardry.registry.WizardrySounds; +import net.minecraft.client.Minecraft; +import net.minecraft.client.audio.PositionedSoundRecord; +import net.minecraft.client.audio.SoundHandler; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +@SideOnly(Side.CLIENT) +class GuiButtonTurnPage extends GuiButton { + + static final int WIDTH = 20; + static final int HEIGHT = 12; + + enum Type { + + NEXT_PAGE(0, 196), + PREVIOUS_PAGE(0, 208), + NEXT_SECTION(0, 220), + PREVIOUS_SECTION(0, 232), + CONTENTS(0, 244); + + private final int u, v; + + Type(int u, int v){ + this.u = u; + this.v = v; + } + } + + public final Type type; + + private static final ResourceLocation texture = new ResourceLocation(Wizardry.MODID, "textures/gui/handbook.png"); + + public GuiButtonTurnPage(int id, int x, int y, Type type){ + super(id, x, y, WIDTH, HEIGHT, ""); + this.type = type; + } + + @Override + public void playPressSound(SoundHandler soundHandler){ + soundHandler.playSound(PositionedSoundRecord.getMasterRecord(WizardrySounds.MISC_PAGE_TURN, 1)); + } + + @Override + public void drawButton(Minecraft minecraft, int mouseX, int mouseY, float partialTicks){ + + if(this.visible){ + + boolean flag = mouseX >= this.x && mouseY >= this.y && mouseX < this.x + this.width && mouseY < this.y + this.height; + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); + minecraft.getTextureManager().bindTexture(texture); + + DrawingUtils.drawTexturedRect(this.x, this.y, flag ? type.u + width : type.u, type.v, width, height, 512, 256); + } + } +} diff --git a/src/main/java/electroblob/wizardry/client/gui/handbook/GuiWizardHandbook.java b/src/main/java/electroblob/wizardry/client/gui/handbook/GuiWizardHandbook.java new file mode 100644 index 00000000..2c21a59f --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/gui/handbook/GuiWizardHandbook.java @@ -0,0 +1,566 @@ +package electroblob.wizardry.client.gui.handbook; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.client.ClientProxy; +import electroblob.wizardry.client.DrawingUtils; +import electroblob.wizardry.client.gui.GuiButtonInvisible; +import electroblob.wizardry.client.gui.handbook.GuiButtonTurnPage.Type; +import electroblob.wizardry.constants.Constants; +import electroblob.wizardry.constants.Element; +import electroblob.wizardry.constants.Tier; +import electroblob.wizardry.registry.WizardrySounds; +import net.minecraft.client.Minecraft; +import net.minecraft.client.audio.PositionedSoundRecord; +import net.minecraft.client.audio.SoundHandler; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.multiplayer.ClientAdvancementManager; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.resources.IResource; +import net.minecraft.client.resources.IResourceManager; +import net.minecraft.item.ItemStack; +import net.minecraft.util.JsonUtils; +import net.minecraft.util.NonNullList; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.event.entity.player.AdvancementEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.relauncher.ReflectionHelper; +import org.apache.commons.lang3.tuple.Pair; +import org.lwjgl.input.Keyboard; + +import java.awt.*; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.reflect.Field; +import java.util.*; +import java.util.List; + +/** + * GUI class for the wizard's handbook. Like any GUI class, this is instantiated each time the book is opened. As of + * Wizardry 4.2, the handbook text is defined as a JSON file rather than a plain text file, and is loaded only on + * resource pack reload, rather than every time the book is opened. This means all the data structures (sections, images, + * etc.) are built before the GUI instance exists at all. However, since some things depend on positioning, these have to + * be initialised on GUI creation. (Previously, everything was done on GUI load) + * + * @author Electroblob + * @since Wizardry 1.0 + * @see Section + * @see Contents + * @see Image + * @see CraftingRecipe + */ +@Mod.EventBusSubscriber +public class GuiWizardHandbook extends GuiScreen { + + private static final ResourceLocation DEFAULT = new ResourceLocation(Wizardry.MODID, "texts/handbook_en_us.json"); + + static final ResourceLocation texture = new ResourceLocation(Wizardry.MODID, "textures/gui/handbook.png"); + + /** Global Gson instance for the handbook. */ + private static final Gson gson = new Gson(); + + // Formatting markup + + static final char FORMAT_MARKER = '#'; + static final char HYPERLINK_MARKER = '@'; + + static final String IMAGE_TAG = "image"; + static final String RECIPE_TAG = "recipe"; + + static final Map FORMAT_TAGS = new HashMap<>(); + + // Dimension constants + // Private constants are not relevant to book elements, package-protected ones are + + /** The dimensions of the rendered GUI area. */ + static final int GUI_WIDTH = 288, GUI_HEIGHT = 180; + /** The dimensions of the area of a single page in which text can be drawn. */ + static final int PAGE_WIDTH = 120, PAGE_HEIGHT = 140; + /** The distance of the text from the top outside corner of each page. */ + static final int TEXT_INSET_X = 17, TEXT_INSET_Y = 16; + /** The distance of the buttons from the bottom outside corners of the GUI. */ + private static final int BUTTON_INSET_X = 22, BUTTON_INSET_Y = 13; + /** The distance between adjacent buttons. */ + private static final int BUTTON_SPACING = 20; + /** The distance of the page numbers from the bottom of the GUI. */ + private static final int PAGE_NUMBER_INSET = 22; + + // IDEA: Constant dimensions could be converted to JSON like the spell HUD ones + + // Global variables + + /** + * The double-page currently being viewed. Each double-page spread counts as a single page, with the inside + * of the front cover being page 0. + */ + private int currentPage = 0; + /** + * The number of single pages currently in the book. This is calculated on GUI load based on visible sections. + */ + private int pageCount = 1; // Starts at 1 because the first single-page is the inside of the cover + /** + * The double-page number where the bookmark is currently set, relative to the section stored in + * {@link GuiWizardHandbook#bookmarkSection}. Static because it persists when the book is closed. + */ + private static int bookmarkPage = 1; + /** + * A reference to the section in which the bookmark is currently set. Static because it persists when the book is + * closed. Storing a section means the bookmark doesn't change location when new sections are unlocked. + */ + private static Section bookmarkSection; + + // Buttons + private GuiButton bookmark, next, previous, nextSection, previousSection, menu; + + // Handbook content + + // As a general rule, I prefer to make static final fields lowercase if they're collections that change, because even + // though the collection itself is constant, the stuff in it is not, so being lowercase highlights this difference. + + /** + * A map which stores all loaded section objects, including subsections. This gets wiped on resource pack reload and + * repopulated with mappings as specified by the handbook JSON file for the current language. The keys in the map + * correspond to the keys in the sections object in that file, and are sorted in that order. + */ + static final Map sections = new LinkedHashMap<>(); + + /** + * A list which stores all loaded section objects, including subsections. This is an unmodifiable list view of the + * values in {@link GuiWizardHandbook#sections}, sorted in the same (page number) order. This exists only to allow + * sections to be accessed by ordinal index for the various navigation buttons, hence why it is private. + */ + private static List
sectionList; + + /** + * A map which stores all loaded contents objects. This gets wiped on resource pack reload and repopulated with + * mappings as specified by the handbook JSON file for the current language. The keys in the map correspond to the + * id strings for the contents objects in that file. This map is not sorted. + */ + static final Map contentsList = new HashMap<>(); + + /** + * A map which stores all loaded hex colour values. This gets wiped on resource pack reload and repopulated with + * mappings as specified by the handbook JSON file for the current language. The keys in the map correspond to the + * keys in the colours object in that file. This map is not sorted. + */ + static final Map colours = new HashMap<>(); + + /** + * A map which stores all loaded image objects. This gets wiped on resource pack reload and repopulated with + * mappings as specified by the handbook JSON file for the current language. The keys in the map correspond to the + * keys in the images object in that file. This map is not sorted. + */ + static final Map images = new HashMap<>(); + + /** + * A map which stores all loaded crafting recipe objects. This gets wiped on resource pack reload and repopulated + * with mappings as specified by the handbook JSON file for the current language. The keys in the map correspond to + * the keys in the recipes object in that file. This map is not sorted. + */ + static final Map recipes = new HashMap<>(); + + static String rulerText = "--------------------"; // Assigned here as a fallback + + private static final List>>> RECIPES = new ArrayList<>(); + + // Reflection + + static final Field ADVANCEMENT_TO_PROGRESS; + + static { + // TODO: Obf field name + ADVANCEMENT_TO_PROGRESS = ReflectionHelper.findField(ClientAdvancementManager.class, "advancementToProgress"); + } + + /** + * Adds a format tag to the handbook. All occurrences of the given tag string preceded by a # will be replaced with + * the result of the given value string on GUI load. The value string, therefore, can be anything that should be + * input dynamically, as long as it does not change while the GUI is open. Examples include wizardry's version, + * the various element colours and the keys assigned to wizardry's controls. + * @param tag The tag string, as defined in the handbook JSON file, excluding the # character. Cannot include spaces. + * @param value The string to replace occurrences of the given format tag with. Can include spaces but not the # character. + */ + public static void addFormatTag(String tag, String value){ + FORMAT_TAGS.put(tag, value); + } + + private static void initFormatTags(){ + + addFormatTag("next_spell_key", ClientProxy.NEXT_SPELL.getDisplayName()); + addFormatTag("previous_spell_key", ClientProxy.PREVIOUS_SPELL.getDisplayName()); + addFormatTag("mana_per_crystal_minus_30", "" + (Constants.MANA_PER_CRYSTAL - 30)); + addFormatTag("mana_per_crystal", "" + Constants.MANA_PER_CRYSTAL); + addFormatTag("novice_max_charge", "" + Tier.BASIC.maxCharge); + addFormatTag("apprentice_max_charge", "" + Tier.APPRENTICE.maxCharge); + addFormatTag("advanced_max_charge", "" + Tier.ADVANCED.maxCharge); + addFormatTag("master_max_charge", "" + Tier.MASTER.maxCharge); + addFormatTag("version", Wizardry.VERSION); + addFormatTag("mcversion", Minecraft.getMinecraft().getVersion()); + + addFormatTag("colour_novice", "\u00A77"); + addFormatTag("colour_apprentice", Tier.APPRENTICE.getFormattingCode()); + addFormatTag("colour_advanced", Tier.ADVANCED.getFormattingCode()); + addFormatTag("colour_master", Tier.MASTER.getFormattingCode()); + + addFormatTag("colour_fire", Element.FIRE.getFormattingCode()); + addFormatTag("colour_ice", Element.ICE.getFormattingCode()); + addFormatTag("colour_lightning", Element.LIGHTNING.getFormattingCode()); + addFormatTag("colour_necromancy", Element.NECROMANCY.getFormattingCode()); + addFormatTag("colour_earth", Element.EARTH.getFormattingCode()); + addFormatTag("colour_sorcery", Element.SORCERY.getFormattingCode()); + addFormatTag("colour_healing", Element.HEALING.getFormattingCode()); + + addFormatTag("colour_reset", "\u00A70"); + } + + // Helper methods + + /** + * Converts the given single page index to a double-page index. Inverse of + * {@link GuiWizardHandbook#doubleToSinglePage(int, boolean)}. + * + * @param singlePageIndex The single-page index, which is the same as the page numbers actually displayed. + * @return The corresponding double-page index. + */ + static int singleToDoublePage(int singlePageIndex){ + // Yes, this is trivial, but if I ever change the numbering it'll be useful. It's also more descriptive. + return singlePageIndex / 2; + } + + /** + * Converts the given double-page index to a single-page index. Inverse of + * {@link GuiWizardHandbook#singleToDoublePage(int)}. + * + * @param doublePageIndex The double-page index, as stored in {@link GuiWizardHandbook#currentPage}. + * @param rightHandPage True to return the page on the right (1 greater), false for the left-hand page. + * @return The corresponding single-page index. + */ + static int doubleToSinglePage(int doublePageIndex, boolean rightHandPage){ + return rightHandPage ? doublePageIndex * 2 + 1 : doublePageIndex * 2; + } + + /** + * Returns whether the given page index refers to a right-hand page or a left-hand page. + * + * @param page The single-page index, which is the same as the page number actually displayed. + * @return True if the given page index refers to a right-hand page, false if it is a left-hand page. + */ + static boolean isRightPage(int page){ + return page % 2 == 1; + } + + // Drawing + + @Override + public void drawScreen(int mouseX, int mouseY, float partialTicks){ + + int left = this.width / 2 - GUI_WIDTH / 2; + int top = this.height / 2 - GUI_HEIGHT / 2; + + Minecraft.getMinecraft().renderEngine.bindTexture(texture); + + // Main background + DrawingUtils.drawTexturedRect(left, top, 0, 0, GUI_WIDTH, GUI_HEIGHT, 512, 256); + + // First page background + if(currentPage == 0){ + DrawingUtils.drawTexturedRect(left, top, 368, 0, GUI_WIDTH / 2, GUI_HEIGHT, 512, 256); + previous.visible = false; + previousSection.visible = false; // Not worth testing if we're in the first section every frame + menu.visible = false; + }else{ + previous.visible = true; + previousSection.visible = true; + menu.visible = true; + } + + // Last page background + if(currentPage == singleToDoublePage(pageCount)){ + DrawingUtils.drawTexturedFlippedRect(left + GUI_WIDTH / 2, top, 368, 0, GUI_WIDTH / 2, GUI_HEIGHT, 512, 256, true, false); + next.visible = false; + nextSection.visible = false; + }else{ + next.visible = true; + nextSection.visible = true; + } + + // Page numbers + if(currentPage > 0){ + String pageNumber = "" + doubleToSinglePage(currentPage, false); + this.fontRenderer.drawString(pageNumber, left + TEXT_INSET_X + PAGE_WIDTH / 2 + - fontRenderer.getStringWidth(pageNumber)/2, top + GUI_HEIGHT - PAGE_NUMBER_INSET, DrawingUtils.BLACK); + } + if(currentPage < singleToDoublePage(pageCount)){ + String pageNumber = "" + doubleToSinglePage(currentPage, true); + this.fontRenderer.drawString(pageNumber, left + GUI_WIDTH - TEXT_INSET_X - PAGE_WIDTH / 2 + - fontRenderer.getStringWidth(pageNumber)/2, top + GUI_HEIGHT - PAGE_NUMBER_INSET, DrawingUtils.BLACK); + } + + // Main content + images.values().forEach(i -> i.draw(fontRenderer, currentPage, left, top)); + contentsList.values().forEach(c -> c.draw(fontRenderer, currentPage, left, top)); + sections.values().forEach(s -> s.draw(fontRenderer, currentPage, left, top)); + recipes.values().forEach(r -> r.draw(fontRenderer, itemRender, currentPage, left, top)); + + // Buttons + super.drawScreen(mouseX, mouseY, partialTicks); + + // Bookmark + GlStateManager.color(1, 1, 1, 1); + Minecraft.getMinecraft().renderEngine.bindTexture(texture); + + if(currentPage == singleToDoublePage(bookmarkSection.startPage) + bookmarkPage){ + bookmark.visible = false; + DrawingUtils.drawTexturedRect(left + 138, top, 299, 0, 11, 191, 512, 256); + }else{ + bookmark.visible = true; + bookmark.x = left + (currentPage > singleToDoublePage(bookmarkSection.startPage) + bookmarkPage ? 130 : 147); + DrawingUtils.drawTexturedRect(bookmark.x, top, + bookmark.isMouseOver() ? 310 : 288, 0, 11, 191, 512, 256); + } + + // Recipe tooltips + recipes.values().forEach(r -> r.drawTooltips(this, fontRenderer, itemRender, currentPage, left, top, mouseX, mouseY)); + + } + + // GUI Initialisation / Close + + @Override + public void onResize(Minecraft minecraft, int width, int height){ + initGui(); + } + + @Override + public void onGuiClosed(){ + super.onGuiClosed(); + Keyboard.enableRepeatEvents(false); + } + + @Override + public void initGui(){ + + super.initGui(); + Keyboard.enableRepeatEvents(true); + + initFormatTags(); + + final int left = this.width / 2 - GUI_WIDTH / 2; + final int top = this.height / 2 - GUI_HEIGHT / 2; + + recipes.values().forEach(CraftingRecipe::load); + + int nextButtonId = 0; + + this.buttonList.clear(); + + this.buttonList.add(next = new GuiButtonTurnPage(nextButtonId++, left + GUI_WIDTH - BUTTON_INSET_X - GuiButtonTurnPage.WIDTH, + top + GUI_HEIGHT - BUTTON_INSET_Y - GuiButtonTurnPage.HEIGHT, Type.NEXT_PAGE)); + + this.buttonList.add(previous = new GuiButtonTurnPage(nextButtonId++, left + BUTTON_INSET_X, + top + GUI_HEIGHT - BUTTON_INSET_Y - GuiButtonTurnPage.HEIGHT, Type.PREVIOUS_PAGE)); + + this.buttonList.add(nextSection = new GuiButtonTurnPage(nextButtonId++, left + GUI_WIDTH - BUTTON_INSET_X - GuiButtonTurnPage.WIDTH - BUTTON_SPACING, + top + GUI_HEIGHT - BUTTON_INSET_Y - GuiButtonTurnPage.HEIGHT, Type.NEXT_SECTION)); + + this.buttonList.add(previousSection = new GuiButtonTurnPage(nextButtonId++, left + BUTTON_INSET_X + BUTTON_SPACING, + top + GUI_HEIGHT - BUTTON_INSET_Y - GuiButtonTurnPage.HEIGHT, Type.PREVIOUS_SECTION)); + + this.buttonList.add(menu = new GuiButtonTurnPage(nextButtonId++, left + GUI_WIDTH/2 - 28, + top + GUI_HEIGHT - BUTTON_INSET_Y - GuiButtonTurnPage.HEIGHT, Type.CONTENTS)); + + this.buttonList.add(bookmark = new GuiButtonInvisible(nextButtonId++, left + 130, top + 172, 11, 19) { + @Override + public void playPressSound(SoundHandler soundHandler){ + soundHandler.playSound(PositionedSoundRecord.getMasterRecord(WizardrySounds.MISC_PAGE_TURN, 1)); + } + }); + + pageCount = 1; + + // Clears instances of all images and recipes + images.values().forEach(Image::clearInstances); + recipes.values().forEach(CraftingRecipe::clearInstances); + + // Formats all the unlocked sections in order + for(Section section : sections.values()){ + if(section.isUnlocked()){ + pageCount = section.format(this.fontRenderer, pageCount, left, top); + buttonList.addAll(section.getButtons()); + } + } + + contentsList.values().forEach(c -> buttonList.addAll(c.getButtons())); + + this.mc.getSoundHandler().playSound(PositionedSoundRecord.getMasterRecord(WizardrySounds.MISC_BOOK_OPEN, 1)); + } + + // JSON Parsing / Data Construction + + /** + * Called from preInit in the main mod class (via the proxies) to initialise the handbook (parses the JSON file + * and constructs the relevant data structures), and again on each resource reload (changing the language triggers + * a resource reload). + */ + public static void loadHandbookFile(IResourceManager manager){ + + IResource handbookFile = getHandbookResource(manager); + + if(handbookFile != null){ + + // Wipes all the maps before repopulating them + images.clear(); + sections.clear(); + contentsList.clear(); + colours.clear(); + + bookmarkSection = null; // Also need to wipe the reference to the old bookmarked section + + BufferedReader reader = new BufferedReader(new InputStreamReader(handbookFile.getInputStream())); + + JsonElement je = gson.fromJson(reader, JsonElement.class); + JsonObject json = je.getAsJsonObject(); + + rulerText = JsonUtils.getString(json, "ruler_text"); + + JsonUtils.getJsonObject(json, "colours").entrySet().forEach(e -> colours.put(e.getKey(), + Color.decode(e.getValue().getAsString()).getRGB())); + + // Repopulates the remaining maps + Image.populate(images, json); + CraftingRecipe.populate(recipes, json); + Section.populate(sections, json); + + sectionList = Collections.unmodifiableList(new ArrayList<>(sections.values())); + + if(sections.isEmpty()){ + Wizardry.logger.warn("Handbook has no sections! Aborting loading..."); + return; + } + + // Attempts to find a suitable place to put the bookmark to start with + bookmarkSection = sections.get("introduction"); + if(bookmarkSection == null) bookmarkSection = sectionList.get(0); + } + } + + /** + * Retrieves the handbook JSON file for the current language and returns its IResource object. If a handbook file + * cannot be found for the current language, a message is printed to the console and the method attempts to retrieve + * the default file instead (English-US). If this file cannot be found, the resulting error is printed to the + * console and the method returns null. + * + * @param manager The resource manager instance to use. + * @return The handbook JSON file, as an IResource, or null if it was not found. + */ + private static IResource getHandbookResource(IResourceManager manager){ + + IResource handbookFile = null; + + try{ + handbookFile = manager.getResource(new ResourceLocation(Wizardry.MODID, "texts/handbook_" + + Minecraft.getMinecraft().getLanguageManager().getCurrentLanguage().getLanguageCode() + ".json")); + }catch(IOException e){ + + Wizardry.logger.info("Wizard handbook JSON file missing for the current language (" + Minecraft.getMinecraft() + .getLanguageManager().getCurrentLanguage() + "). Using default (English-US) instead."); + + try{ + handbookFile = manager.getResource(DEFAULT); + }catch(IOException x){ + Wizardry.logger.error("Couldn't find file: " + DEFAULT + ". The file may be missing; please try re-downloading and reinstalling Wizardry.", x); + } + } + + return handbookFile; + } + + // Controls + + @Override + protected void actionPerformed(GuiButton button){ + + if(button.enabled){ + + if(button == next){ + if(currentPage < singleToDoublePage(pageCount)) currentPage++; + + }else if(button == previous){ + if(currentPage > 0) currentPage--; + + }else if(button == nextSection || button == previousSection){ + + Section currentSection = null; + + for(Section section : sections.values()){ + // We always want this button to do something, and taking the right-hand page means it always does + if(section.containsPage(doubleToSinglePage(currentPage, true))){ + currentSection = section; + break; + } + } + + if(currentSection != null){ + + int index = sectionList.indexOf(currentSection); + + if(button == nextSection && index + 1 < sections.size()){ + currentPage = singleToDoublePage(sectionList.get(index + 1).startPage); + }else if(index > 0){ + currentPage = singleToDoublePage(sectionList.get(index - 1).startPage); + } + } + + }else if(button == menu){ + currentPage = singleToDoublePage(sections.get("main_contents").startPage); + + }else if(button == bookmark && bookmarkSection != null){ + currentPage = singleToDoublePage(bookmarkSection.startPage) + bookmarkPage; + + }else{ + if(button instanceof GuiButtonHyperlink.Internal){ + currentPage = singleToDoublePage(((GuiButtonHyperlink.Internal)button).target.startPage); + }else if(button instanceof GuiButtonHyperlink.External){ + this.handleComponentClick(((GuiButtonHyperlink.External)button).link); + } + } + } + } + + @Override + protected void mouseClicked(int mouseX, int mouseY, int mouseButton) throws IOException{ + if(mouseButton == 1){ + // Right-clicking of bookmark + if(bookmark.mousePressed(this.mc, mouseX, mouseY)){ + + this.selectedButton = bookmark; + + for(Section section : sections.values()){ + // The bookmark is assumed to bookmark the left-hand page + if(section.containsPage(doubleToSinglePage(bookmarkPage, false))) bookmarkSection = section; + } + + bookmarkPage = currentPage - singleToDoublePage(bookmarkSection.startPage); + } + }else{ + super.mouseClicked(mouseX, mouseY, mouseButton); + } + } + + // Overridden to make it public + @Override + public void renderToolTip(ItemStack stack, int x, int y){ + super.renderToolTip(stack, x, y); + } + + @SubscribeEvent + public static void onAdvancementEvent(AdvancementEvent event){ + sections.values().forEach(s -> s.onAdvancement(event.getEntityPlayer(), event.getAdvancement())); + } + +} \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/client/gui/handbook/Image.java b/src/main/java/electroblob/wizardry/client/gui/handbook/Image.java new file mode 100644 index 00000000..cb0d391e --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/gui/handbook/Image.java @@ -0,0 +1,119 @@ +package electroblob.wizardry.client.gui.handbook; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonSyntaxException; +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.util.JsonUtils; +import net.minecraft.util.ResourceLocation; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +class Image { + + // Final fields are mandatory, the rest are optional + private final ResourceLocation location; + private final int width, height; + private int textureWidth, textureHeight; + private int u = 0, v = 0; + private String caption = ""; + // Derived fields, not specifically defined in JSON + private final Set instances = new HashSet<>(); + + private static final int CAPTION_OFFSET = 4; + + private Image(ResourceLocation location, int width, int height){ + this.location = location; + this.width = width; + this.height = height; + } + + /** Returns the width of the image. */ + int getWidth(){ + return width; + } + + /** Returns the total height of the image, including caption if it has one. */ + int getHeight(FontRenderer font){ + return caption.isEmpty() ? height : height + CAPTION_OFFSET + font.FONT_HEIGHT; + } + + /** + * Adds an instance of this image 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 image from the list. */ + void clearInstances(){ + instances.clear(); + } + + /** + * Draws all instances of this image that are located on the given double-page spread. + * + * @param font The font 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, int doublePage, int left, int top){ + for(int[] instance : instances){ + if(GuiWizardHandbook.singleToDoublePage(instance[0]) == doublePage){ + Minecraft.getMinecraft().renderEngine.bindTexture(location); + GlStateManager.color(1, 1, 1, 1); + DrawingUtils.drawTexturedRect(left + instance[1], top + instance[2], u, v, width, height, textureWidth, textureHeight); + font.drawString("\u00A7o" + caption, left + instance[1] + width/ 2 - font.getStringWidth(caption)/2, + top + instance[2] + height + CAPTION_OFFSET, GuiWizardHandbook.colours.get("caption")); + } + } + } + + /** + * 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 "location" + * string. + * @return The resulting {@code Image} object. + * @throws JsonSyntaxException if at any point the JSON object is found to be invalid. + */ + static Image fromJson(JsonObject json){ + + Image image = new Image(new ResourceLocation(JsonUtils.getString(json, "location")), + JsonUtils.getInt(json, "width"), JsonUtils.getInt(json, "height")); + + image.u = JsonUtils.getInt(json, "u", 0); + image.v = JsonUtils.getInt(json, "v", 0); + image.textureWidth = JsonUtils.getInt(json, "texture_width", image.width); + image.textureHeight = JsonUtils.getInt(json, "texture_height", image.height); + image.caption = JsonUtils.getString(json, "caption", ""); + + + return image; + } + + static void populate(Map map, JsonObject json){ + + JsonObject sectionsObject = JsonUtils.getJsonObject(json, "images"); + + // 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 + + Image image = fromJson(entry.getValue().getAsJsonObject()); + map.put(key, image); + } + } +} diff --git a/src/main/java/electroblob/wizardry/client/gui/handbook/Section.java b/src/main/java/electroblob/wizardry/client/gui/handbook/Section.java new file mode 100644 index 00000000..29f5fc0e --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/gui/handbook/Section.java @@ -0,0 +1,429 @@ +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 electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.advancements.Advancement; +import net.minecraft.advancements.AdvancementProgress; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.JsonUtils; +import org.apache.commons.lang3.StringUtils; + +import java.util.*; + +/** + * Instances of this class represent sections in the wizard's handbook. As of wizardry 4.2, this class handles + * everything within the section itself, including JSON parsing, unlock triggers and drawing the actual rawText. + * Sections may now also be nested and have other elements within them, such as images and a table of contents, a + * behaviour which is also handled within this class. + *

+ * The formatting of the book is now done 'dynamically' - that is, the exact positions and page numbers of + * sections, images and so on are determined on GUI load and depend on which of the previous sections have been + * unlocked, amongst other factors. This means that all of the unlocked sections must be formatted in order on GUI + * load, so that each section knows the previous section's length and therefore where to start. + * + * @author Electroblob + * @since Wizardry 4.2 + */ +// Because these are now generated on resource pack reload (not on handbook open, as before), this class must now +// be static +class Section { + + // Final fields are mandatory (none here though), the rest are optional + String title; + private String[] rawText; + private Contents contents; + private Advancement[] triggers; + private Map subsections; + private boolean centreX, centreY; + + // Derived fields, not explicitly defined in JSON + /** The single-page index of the first page of this section. */ + int startPage; + private final List> buttons; + /** + * A list of single pages, which are themselves lists of paragraphs (each paragraph is a single + * string which may include line breaks and other escape characters). + */ + private final List> pages; + + private Section(){ + this.buttons = new ArrayList<>(); + this.pages = new ArrayList<>(); + this.subsections = new LinkedHashMap<>(); + } + + Collection getButtons(){ + return WizardryUtilities.flatten(buttons); + } + + /** + * Returns true if the given page is within this section, false if not. + */ + boolean containsPage(int page){ + return startPage <= page && startPage + pages.size() > page; + } + + /** + * Returns true if this section is unlocked for the client player, false if not. Always returns true if + * handbook progression is disabled in the config. + */ + boolean isUnlocked(){ + + if(!Wizardry.settings.handbookProgression) return true; // Always unlocked if handbook progression is off + + // A section is automatically unlocked if one of its subsections is unlocked + for(Section subsection : subsections.values()){ + if(subsection.isUnlocked()) return true; + } + + if(triggers == null) return true; // If no triggers were defined, the section is unlocked from the start + + for(Advancement trigger : triggers){ + + try{ + + // TESTME: Is this properly synced or do we need to do that as well? + Map advancements = (Map)GuiWizardHandbook.ADVANCEMENT_TO_PROGRESS.get(Minecraft.getMinecraft().player.connection.getAdvancementManager()); + + if(advancements.get(trigger).isDone()){ + return true; + } + + }catch(IllegalAccessException e){ + e.printStackTrace(); + } + } + + return false; + } + + /** + * Actually draws the contents of the given section for the given double-page spread. Will do nothing if the + * given page is outside of this section. + * + * @param font The font renderer object. + * @param doublePage The index of the double-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. + */ + // This method is supposed to be 'idiot-proof' in the sense that the code calling it need not check whether the + // section actually needs drawing, so it can just dumbly call draw(...) for all the sections in order. + void draw(FontRenderer font, int doublePage, int left, int top){ + + // Show/hide buttons + + int i = 0; + + for(List list : buttons){ + final int i1 = i++; + list.forEach(b -> b.visible = GuiWizardHandbook.singleToDoublePage(startPage + i1) == doublePage); + } + + int leftIndex = GuiWizardHandbook.doubleToSinglePage(doublePage, false); + // Relative indices of the pages to be rendered - often these will be outside the section entirely + int[] visiblePages = {leftIndex - startPage, leftIndex - startPage + 1}; + + for(int page : visiblePages){ + + if(page >= 0 && page < pages.size()){ + + List lines = pages.get(page); + + int x = left + (GuiWizardHandbook.isRightPage(startPage + page) ? GuiWizardHandbook.GUI_WIDTH - GuiWizardHandbook.TEXT_INSET_X - GuiWizardHandbook.PAGE_WIDTH : GuiWizardHandbook.TEXT_INSET_X); + int y = top + GuiWizardHandbook.TEXT_INSET_Y; + if(centreY) y += GuiWizardHandbook.PAGE_HEIGHT / 2 - lines.size() / 2 * font.FONT_HEIGHT; + + for(String line : lines){ + + int lx = centreX ? x + GuiWizardHandbook.PAGE_WIDTH / 2 - font.getStringWidth(line) / 2 : x; + + font.drawString(line, lx, y, DrawingUtils.BLACK, false); + + y += font.FONT_HEIGHT; + } + } + } + } + + /** + * Called on GUI load to format the section, contents tables and other elements, excluding subsections. + * Does not perform any actual drawing. + * + * @param font The font renderer object, for measurement purposes. + * @param startPage The index of the first page (single side, not double-page) of this section. + * @param left The x coordinate of the left side of the GUI. + * @param top The y coordinate of the top of the GUI. + * @return The single-page index of the next blank page after the end of this section. + * @throws JsonSyntaxException if at any point the formatting is found to be invalid. + */ + int format(FontRenderer font, int startPage, int left, int top){ + + this.buttons.clear(); + this.pages.clear(); + + // FONT_HEIGHT may change between fonts, so this is calculated here. With the default font it's 14. + final int maxLineNumber = GuiWizardHandbook.PAGE_HEIGHT / font.FONT_HEIGHT; + + this.startPage = startPage; + + // First everything is added to a single list of lines, then it is split into pages. + List lines = new ArrayList<>(); + + // Adds the header if present + if(!this.title.isEmpty()){ + lines.add(this.title); + lines.add(GuiWizardHandbook.rulerText); + } + + // Adds space for the contents if it exists + if(this.contents != null){ + lines.addAll(Collections.nCopies(this.contents.format(font, startPage, lines.size(), left, top), "")); + // Line break between contents and first paragraph + if((lines.size() % maxLineNumber) != 0) lines.add(""); + } + + if(this.rawText != null){ + // Paragraphs are defined as a JSON list because it makes it easier to arrange them properly across pages + // - using multiple line breaks would mean having to find and remove them when at the top of a page. + for(String paragraph : this.rawText){ + + // (lines.size() % maxLineNumber) gives the number of lines on the current page + // (lines.size() / maxLineNumber) gives the index of the current page minus the value of startPage + + // Formats the paragraph + + String raw = paragraph; // For error messages + + // Images (images must be separate paragraphs) + + if(paragraph.startsWith(GuiWizardHandbook.FORMAT_MARKER + GuiWizardHandbook.IMAGE_TAG)){ + + String[] arguments = paragraph.split("\\s", 2); + + if(arguments.length < 2) throw new JsonSyntaxException("Missing image name in string " + + StringUtils.abbreviate(raw, 50)); + + Image image = GuiWizardHandbook.images.get(arguments[1]); + if(image == null) throw new JsonSyntaxException("Image with id " + arguments[1] + "is undefined"); + + // Starts a new page if the image will not fit on the current one + if((lines.size() % maxLineNumber) * font.FONT_HEIGHT + image.getHeight(font) > GuiWizardHandbook.PAGE_HEIGHT){ + // Remaining number of lines on the page + lines.addAll(Collections.nCopies(maxLineNumber - (lines.size() % maxLineNumber), "")); + } + + if(image.getWidth() > GuiWizardHandbook.PAGE_WIDTH) Wizardry.logger.warn("Image with id " + arguments[1] + + "has a width (" + image.getWidth() + ") greater than the maximum page width (" + GuiWizardHandbook.PAGE_WIDTH + + "), it will extend beyond the page area."); + + if(image.getHeight(font) > GuiWizardHandbook.PAGE_HEIGHT) Wizardry.logger.warn("Image with id " + arguments[1] + + "has a height (" + image.getHeight(font) + ") greater than the maximum page height (" + GuiWizardHandbook.PAGE_HEIGHT + + "), it will extend beyond the page area."); + + int page = startPage + (lines.size() / maxLineNumber); + + image.addInstance(page, GuiWizardHandbook.PAGE_WIDTH / 2 - image.getWidth() / 2 + + (GuiWizardHandbook.isRightPage(page) ? GuiWizardHandbook.GUI_WIDTH - GuiWizardHandbook.TEXT_INSET_X - GuiWizardHandbook.PAGE_WIDTH : GuiWizardHandbook.TEXT_INSET_X), + GuiWizardHandbook.TEXT_INSET_Y + (lines.size() % maxLineNumber) * font.FONT_HEIGHT); + + // Height of the image in lines, rounded up + // Uses a single space instead of an empty string so that the page trimming doesn't remove them + lines.addAll(Collections.nCopies(image.getHeight(font) / font.FONT_HEIGHT, " ")); + lines.add(""); // The last one is removable though, since it's actually extra space + + // Recipes (recipes must be separate paragraphs) + }else if(paragraph.startsWith(GuiWizardHandbook.FORMAT_MARKER + GuiWizardHandbook.RECIPE_TAG)){ + + String[] arguments = paragraph.split("\\s", 2); + + if(arguments.length < 2) throw new JsonSyntaxException("Missing recipe name in string " + + StringUtils.abbreviate(raw, 50)); + + CraftingRecipe recipe = GuiWizardHandbook.recipes.get(arguments[1]); + if(recipe == null) throw new JsonSyntaxException("Recipe with id " + arguments[1] + "is undefined"); + + // Starts a new page if the recipe will not fit on the current one + if((lines.size() % maxLineNumber) * font.FONT_HEIGHT + CraftingRecipe.HEIGHT > GuiWizardHandbook.PAGE_HEIGHT){ + // Remaining number of lines on the page + lines.addAll(Collections.nCopies(maxLineNumber - (lines.size() % maxLineNumber), "")); + } + + int page = startPage + (lines.size() / maxLineNumber); + + recipe.addInstance(page, GuiWizardHandbook.PAGE_WIDTH / 2 - CraftingRecipe.WIDTH / 2 + + (GuiWizardHandbook.isRightPage(page) ? GuiWizardHandbook.GUI_WIDTH - GuiWizardHandbook.TEXT_INSET_X - GuiWizardHandbook.PAGE_WIDTH : GuiWizardHandbook.TEXT_INSET_X), + GuiWizardHandbook.TEXT_INSET_Y + (lines.size() % maxLineNumber) * font.FONT_HEIGHT); + + // Height of the recipe in lines, rounded up + // Uses a single space instead of an empty string so that the page trimming doesn't remove them + lines.addAll(Collections.nCopies(CraftingRecipe.HEIGHT / font.FONT_HEIGHT, " ")); + // This time we're not adding an extra space because it's not really needed + + }else{ // All other paragraphs + + // Hyperlinks + + int linkStart; + + while((linkStart = paragraph.indexOf(GuiWizardHandbook.HYPERLINK_MARKER)) > -1){ // Ooh an assignment and a comparison in one... + + int linkEnd = paragraph.indexOf(GuiWizardHandbook.HYPERLINK_MARKER, linkStart + 1); + + if(linkEnd < 0) throw new JsonSyntaxException("Un-closed hyperlink marker in string " + + StringUtils.abbreviate(raw, 50)); + + List upToLink = font.listFormattedStringToWidth(paragraph.substring(0, linkStart), GuiWizardHandbook.PAGE_WIDTH); + + String linkRaw = paragraph.substring(linkStart, linkEnd + 1); + String[] arguments = paragraph.substring(linkStart + 1, linkEnd).split("\\s", 2); + String suffix = paragraph.substring(linkEnd).split("\\s", 2)[0]; + + // The index of the single page currently being formatted, relative to the section + int pageRelative = (lines.size() + upToLink.size() - 1) / maxLineNumber; + // The overall index of the single page currently being formatted + int page = startPage + pageRelative; + + int x = GuiWizardHandbook.isRightPage(page) ? left + GuiWizardHandbook.GUI_WIDTH - GuiWizardHandbook.TEXT_INSET_X - GuiWizardHandbook.PAGE_WIDTH : left + GuiWizardHandbook.TEXT_INSET_X; + int y = top + GuiWizardHandbook.TEXT_INSET_Y + (((lines.size() + upToLink.size() - 1) % maxLineNumber)) * font.FONT_HEIGHT; + + // Adds any missing sub-lists + while(this.buttons.size() <= pageRelative){ + this.buttons.add(new ArrayList<>()); + } + + // The button id only does what you use it for, so we're just not using it at all. + this.buttons.get(pageRelative).add(GuiButtonHyperlink.create(x, y, font, upToLink, arguments, suffix)); + + // The link button should exactly overlay the display rawText in the main string + // If the link has no display rawText specified, it displays the unformatted target string + paragraph = paragraph.replace(linkRaw, arguments[arguments.length - 1]); + } + + // Formatting + for(Map.Entry entry : GuiWizardHandbook.FORMAT_TAGS.entrySet()){ + paragraph = paragraph.replace(GuiWizardHandbook.FORMAT_MARKER + entry.getKey(), entry.getValue()); + } + + lines.addAll(font.listFormattedStringToWidth(paragraph, GuiWizardHandbook.PAGE_WIDTH)); + } + + // Line break between paragraphs (the last one will just be deleted later) + if((lines.size() % maxLineNumber) != 0) lines.add(""); + } + } + + // Splits lines into pages + + List page = new ArrayList<>(); + pages.add(page); + + while(!lines.isEmpty()){ + + if(page.size() == maxLineNumber){ + // Removes blank lines at the end of the page + while(page.get(page.size() - 1).isEmpty()) page.remove(page.size() - 1); + // Adds a new page + pages.add(page = new ArrayList<>()); + } + + String line = lines.remove(0); + + // Prevents blank lines at the start of the page + if(!page.isEmpty() || !line.isEmpty()) page.add(line); + } + + return startPage + pages.size(); + } + + /** + * Parses the given JSON object and constructs a new {@code Section} from it, setting all the relevant fields + * and references. This method converts the JSON object to a {@code Section} object and retrieves any resources; + * the section is not formatted in any way until GUI load, in {@link Section#format(FontRenderer, int, int, int)}. + * + * @param json A JSON object representing the section to be constructed. This must contain at least a "title" + * string. + * @return The resulting {@code Section} object. + * @throws JsonSyntaxException if at any point the JSON object is found to be invalid. + */ + static Section fromJson(JsonObject json){ + + Section section = new Section(); + + section.title = JsonUtils.getString(json, "title", ""); + + if(JsonUtils.hasField(json, "include_in_contents")){ + + String id = JsonUtils.getString(json, "include_in_contents"); + + Contents belongsTo = GuiWizardHandbook.contentsList.get(id); + + if(belongsTo == null){ + throw new JsonSyntaxException("Expected include_in_contents to be the id of a previously defined contents, but no contents with the id " + id + " exists yet."); + }else{ + belongsTo.addEntry(section); + } + } + + if(JsonUtils.hasField(json, "contents")){ + section.contents = Contents.fromJson(JsonUtils.getJsonObject(json, "contents")); + GuiWizardHandbook.contentsList.put(section.contents.id, section.contents); + } + + if(JsonUtils.hasField(json, "text")){ + // TESTME: Does this always preserve order? + section.rawText = Streams.stream(JsonUtils.getJsonArray(json, "text")) + .map(e -> JsonUtils.getString(e, "element of array rawText")) + .toArray(String[]::new); + } + + // TODO: Triggers + + if(JsonUtils.hasField(json, "centre")){ + JsonObject centre = JsonUtils.getJsonObject(json,"centre"); + section.centreX = JsonUtils.getBoolean(centre, "x", false); + section.centreY = JsonUtils.getBoolean(centre, "y", false); + } + + // The only benefit of having subsections (other than logical grouping) is that the parent section can + // automatically be unlocked if one of the subsections is. + if(JsonUtils.hasField(json, "sections")){ + populate(section.subsections, json); + } + + return section; + } + + static void populate(Map map, JsonObject json){ + + JsonObject sectionsObject = JsonUtils.getJsonObject(json, "sections"); + + // 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 + + Section section = fromJson(entry.getValue().getAsJsonObject()); + map.put(key, section); + map.putAll(section.subsections); + } + } + + public void onAdvancement(EntityPlayer player, Advancement advancement){ + Minecraft minecraft = Minecraft.getMinecraft(); + //System.out.println(title); + if(triggers != null && Arrays.asList(triggers).contains(advancement) && player == minecraft.player){ + //minecraft.getToastGui().drawToast(IToast); + + // Debug + //System.out.println(title); + } + } +} diff --git a/src/main/resources/assets/ebwizardry/texts/handbook_en_gb.json b/src/main/resources/assets/ebwizardry/texts/handbook_en_gb.json new file mode 100644 index 00000000..8eddb4be --- /dev/null +++ b/src/main/resources/assets/ebwizardry/texts/handbook_en_gb.json @@ -0,0 +1,528 @@ +{ + "ruler_text": "--------------------", + + "colours": { + "text": "#000000", + "caption": "#666666", + "hyperlink": "#601ba0", + "highlight": "#dd4c1d" + }, + + "images": { + "workbench": { + "location": "ebwizardry:textures/gui/arcane_workbench_picture.png", + "caption": "The Arcane Workbench", + "u": 0, + "v": 0, + "width": 110, + "height": 110 + }, + "crystal_ore": { + "location": "ebwizardry:textures/gui/ore_picture.png", + "caption": "Crystal Ore", + "u": 0, + "v": 0, + "width": 64, + "height": 64 + }, + "magic_crystal": { + "location": "ebwizardry:textures/items/crystal_magic.png", + "caption": "A Magic Crystal", + "u": 6, + "v": 6, + "width": 36, + "height": 33, + "texture_width": 48, + "texture_height": 48 + }, + "magic_wand": { + "location": "ebwizardry:textures/items/wand_basic.png", + "caption": "A Magic Wand", + "u": 0, + "v": 0, + "width": 64, + "height": 64 + }, + "crystal_flower": { + "location": "ebwizardry:textures/gui/flower_picture.png", + "caption": "A Crystal Flower", + "u": 0, + "v": 0, + "width": 64, + "height": 64 + } + }, + + "recipes": { + "arcane_workbench": { + "location": "ebwizardry:arcane_workbench" + }, + "magic_wand": { + "location": "ebwizardry:magic_wand" + }, + "magic_missile_spell_book": { + "location": "ebwizardry:magic_missile_spell_book" + }, + "wizard_handbook": { + "location": "ebwizardry:wizard_handbook" + }, + "crystal_flower_to_crystals": { + "location": "ebwizardry:crystal_flower_to_crystals" + }, + "mana_flask": { + "location": "ebwizardry:mana_flask" + }, + "transportation_stone": { + "location": "ebwizardry:transportation_stone" + }, + "magic_silk": { + "location": "ebwizardry:magic_silk" + }, + "wizard_hat": { + "location": "ebwizardry:wizard_hat" + }, + "wizard_robe": { + "location": "ebwizardry:wizard_robe" + }, + "wizard_leggings": { + "location": "ebwizardry:wizard_leggings" + }, + "wizard_boots": { + "location": "ebwizardry:wizard_boots" + }, + "blank_scroll": { + "location": "ebwizardry:blank_scroll" + }, + "firebomb": { + "location": "ebwizardry:firebomb" + }, + "poison_bomb": { + "location": "ebwizardry:poison_bomb" + }, + "smoke_bomb": { + "location": "ebwizardry:smoke_bomb" + } + }, + + "sections": { + + "inside_cover": { + "centre": { + "x": true, + "y": true + }, + "text": [ + + "The Wizard's Handbook", + + "By Electroblob" + ] + }, + + "introduction": { + "title": "Introduction", + "text": [ + + "Greetings, wizard! This book explains the many ways of the arcane and how to use them. This is no ordinary book, though - its pages will materialise before you as you discover more about the magical world.", + + "Use the arrow buttons to turn between pages, and the double-arrow buttons to quickly flip between sections. Use the central menu button to return to the main contents page.", + + "Click on any purple link text to jump straight to the relevant page. Right-click the bookmark to move it to the current page, and left-click to return to the bookmarked page." + ] + }, + + "main_contents": { + "title": "Contents", + "contents": { + "id": "main_contents", + "hyperlinks": true, + "page_numbers": true, + "separator": "." + } + }, + + "setting_up": { + "title": "Setting Up", + "include_in_contents": "main_contents", + "text": [ + + "To get started with wizardry, you will need a few things:", + + "- A magic wand, crafted with a gold nugget, a stick, and a magic crystal.", + + "- An @arcane_workbench arcane workbench@, crafted with 3 stone, 1 lapis lazuli block, 2 magic crystals, 2 gold nuggets and 1 purple carpet.", + + "- A @spells spell@ book. These can be found in chests, dropped as loot, purchased from wizards, or you can make a beginner's spell, magic missile, with a book and 4 magic crystals.", + + "You will also need a bunch more magic crystals to supply your wand with @mana@." + ] + }, + + "mana": { + "title": "Mana", + "include_in_contents": "main_contents", + "triggers": [ + "ebwizardry:crystal" + ], + "text": [ + + "Mana is the arcane energy that gives wizards their powers. It is not a physical substance in its own right; rather, it is an everpresent aura which permeates everything in the world. In particular, it manifests itself in naturally occurring crystals, which can be found embedded in rocks underground. @wands Wands@ can store mana within them, and this mana is channelled into the @spells@ that you cast. Different spells require different amounts of mana, depending on how powerful they are and how long they last.", + + "#image crystal_ore", + + "#image magic_crystal", + + "It is widely accepted that mana cannot be created or destroyed, and that when a spell is cast, the mana it channels is simply dissipated into the surroundings. Indeed, this is how the vast majority of mana exists - spread thinly throughout the world. However, to be of any use, it must be concentrated, either naturally over thousands of years, as is the case with crystals underground, or artificially - an advanced subject covered later in this book. There also exist various ways of making spells more mana-efficient and recovering some of the mana dissipated during spellcasting." + ] + }, + + "wands": { + "title": "Wands", + "include_in_contents": "main_contents", + "triggers": [ + "ebwizardry:arcane_initiate" + ], + "text": [ + + "The wand is the implement of choice for a wizard. With it, you can cast any @spells spell@ provided the wand can contain its power (see @tiers Tiers@). Wands come in many different varieties but you will almost certainly start with a basic magic wand.", + + "#image magic_wand", + + "Most wands begin their life as a simple arrangement of a magic crystal of some sort attached to a wooden stick, with a gold nugget affixed to the other end. The crystal is, of course, the source of the wand's power, as it provides the focus necessary to channel @spells@. The rest of the wand is important too though, as it provides not only a means with which to hold the wand, but also a path through which @mana@ can be channelled and directed.", + + "As more spells are cast with a wand, it grows more effective at channelling @spells@, and can therefore cast spells of greater power. This effect can be discerned from subtle changes that occur in the shape and appearance of the wand itself: as a wand grows more powerful, its crystal will become more vibrant, it may change in colour depending on its element, and after a while, the wood from which it is made will start to grow and twist into more complex shapes.", + + "When holding a wand, a small heads-up display will show in the corner of the screen. This indicates the spell that is currently selected along with a pictorial representation of it, and, if the spell has been cast, a cooldown bar indicating the time until that spell can be cast again. It also shows the names of the next and previous spells bound to the wand. To switch between spells, use the #next_spell_key and #previous_spell_key keys (these can be changed in options -> controls). You can also switch spells by scrolling the mouse wheel while sneaking.", + + "When viewing an inventory, hovering over a wand will display how much @mana@ is stored in it, along with its currently selected @spells spell@ and any specific abilities it may have. More in-depth information about a wand can be viewed by placing it in an @arcane_workbench arcane workbench@." + ] + }, + + "spells": { + "title": "Spells", + "include_in_contents": "main_contents", + "triggers": [ + "ebwizardry:handbook/spells" + ], + "text": [ + + "A @wands wand@ is useless without spells to cast with it. Spells are recorded in two forms: in books, which are permanent, and in scrolls, which are temporary.", + + "Spell books are of little use on their own; instead they are used to bind the spell they contain to @wands@. Spell books can be found throughout the world and it will not take long for you to come across one. When you do find one, you can mouse over the spell book to see basic information about the spell at a glance. You can also right-click whilst holding a spell book to read more detailed information about the spell.", + + "Scrolls, on the other hand, serve a different purpose: right-clicking whilst holding one will cast the spell that is bound to it, destroying the scroll in the process. Like spell books, scrolls can be found throughout the world, but you can also make them yourself by crafting a blank scroll from a piece of paper and some string. You can then bind a spell to the scroll using the @arcane_workbench arcane workbench@, but only if you have knowledge of that spell (see @enchanting_scrolls Enchanting Scrolls@). Scrolls are quite useful when you need to use a spell once or twice on a particular quest but don't want it to take up a slot on your @wands wand@. It should be noted, however, that scrolls are at best an inefficient method of casting spells.", + + "To the untrained eye, the magical runes with which spells are written are unreadable. In order to identify a spell, you could, of course, cast it and see what happens - though doing so may cause unwanted side-effects, and many spells need certain conditions in order to work. A safer and more reliable option is to use a scroll of identification, which can be found in chests or purchased from a wizard. Right-clicking whilst holding one will identify the first unknown spell book or scroll on your hotbar, consuming the scroll of identification in the process." + ] + }, + + "arcane_workbench": { + "title": "Arcane Workbench", + "include_in_contents": "main_contents", + "contents": { + "id": "arcane_workbench_subsections", + "hyperlinks": true, + "page_numbers": true, + "separator": "." + }, + "triggers": [ + "ebwizardry:handbook/arcane_workbench" + ], + "text": [ + + "The arcane workbench is a block similar in appearance to the enchantment table where you can charge, upgrade, and bind @spells@ to your @wands wand@. Simply place it anywhere and right click, and you will see something like this:", + + "#image workbench" + ], + "sections": { + "binding_spells": { + "title": "Binding Spells", + "include_in_contents": "arcane_workbench_subsections", + "text": [ + + "To bind a spell to your wand, simply place your wand in the central slot of the workbench, then place the spell book in one of the surrounding slots and press apply. You will notice that the spell book is left untouched during this process - this is because the act of spell binding does not 'take' the spell from the spell book; rather, it attunes the wand to the spell. As such, you may change the spells bound to your wand as much as you wish by simply repeating the spell binding process. You can bind up to five spells to a wand at one time, though this number may be increased with attunement upgrades." + ] + }, + "charging_wands": { + "title": "Charging Wands", + "include_in_contents": "arcane_workbench_subsections", + "text": [ + + "To charge your wand, place the wand in the central slot of the arcane workbench and place some magic crystals in the upper of the two slots on the left, and then press apply. Each crystal is worth #mana_per_crystal mana, and the wand will only take what it needs, so you can keep a supply of crystals in the workbench for when you need them. However, bear in mind that the wand can only take a whole number of crystals so if the wand needs, for example, 30 more mana, #mana_per_crystal_minus_30 mana will be lost when charging it." + ] + }, + "upgrading_wands": { + "title": "Upgrading Wands", + "include_in_contents": "arcane_workbench_subsections", + "text": [ + + "To upgrade your wand, you will need a tome of arcana of the appropriate tier (see @tiers Tiers@) or a special wand upgrade. Both of these can be found as loot or purchased from a wizard. To upgrade your wand, place it in the central slot and the upgrade in the lower of the two slots on the left, then press apply.", + + "Special wand upgrades improve a particular aspect of a wand. Each type of upgrade can stack up to three times, and the total number of upgrades that a wand can take depends on its tier. Upgrades cannot be removed." + ] + }, + "enchanting_scrolls": { + "title": "Enchanting Scrolls", + "include_in_contents": "arcane_workbench_subsections", + "text": [ + + "The arcane workbench can also be used to enchant spell scrolls. To do so, you will require a blank scroll, crafted from a piece of paper and some string, a spell book for your chosen spell, and enough magic crystals to provide mana to cast it. Place the crystals in the upper left-hand slot, the blank scroll in the central slot, and the spell book in the single slot that appears above it, then press apply to enchant the scroll." + ] + } + } + }, + + "wizard_armour": { + "title": "Wizard Armour", + "include_in_contents": "main_contents", + "text": [ + + "As a wizard, you will need something to protect you from the creatures you fight. No ordinary armour will do though. To make the most of your spells, you will need wizard armour: hat, robes, leggings and boots. Unlike ordinary armour, these will not break. Instead, they use arcane energy to shield the wearer. This of course means that they must be charged with magic crystals, just like wands. Fortunately, these garments do not consume much mana. Charge them as you would a wand in the arcane workbench or with a mana flask, but be careful - if they run out of mana, you will find yourself defenceless.", + + "You can obtain wizard armour by crafting it from magical silk, obtained by crafting string with a magic crystal.", + + "Those wizards who devote themselves to the practice of one element sometimes wear special garments which grant bonuses for that element. Full sets of such garments are very sought after among wizards and are not easy to find.", + + "Legend speaks of an arcane seal used by the most powerful wizards to greatly improve their armour." + ] + }, + + "magical_world": { + "title": "Magic in the World", + "include_in_contents": "main_contents", + "contents": { + "id": "magical_world_subsections", + "hyperlinks": true, + "page_numbers": true, + "separator": "." + }, + "text": [ + + "One cannot master the arcane just by staying at home - there is a world full of magic out there to explore! Ancient ruins, relics and mysterious beings good and bad await discovery - if you know where to look. The greatest wizards are also the most curious, and gain much of their knowledge and power through exploration of the environment around them, and experimenting with what they find.", + + "Whilst much of a wizard's time is inevitably spent alone, the sharing of knowledge is also vital to learning the arcane arts. Seek out fellow practicioners of magic, and learn as much as you can from them: communication is arguably the greatest power of all." + ], + "sections": { + "crystal_flowers": { + "title": "Crystal Flowers", + "include_in_contents": "magical_world_subsections", + "triggers": [ + "ebwizardry:handbook/crystal_flowers" + ], + "text": [ + + "During your travels, you will probably come across curious glowing flowers growing in the wild from time to time. These distinctive blooms are known as crystal flowers, and they are strangely effective at concentrating mana - to this day, nobody is quite sure why. We do know, however, that they can be harvested and crafted to extract the mana as crystals, making them a rather useful above-ground source of mana. The amount of mana obtainable this way is limited, though, by the small size of the flowers and due to them only growing in small patches.", + + "#image crystal_flower" + ] + }, + "wizard_towers": { + "title": "Wizard Towers", + "include_in_contents": "magical_world_subsections", + "text": [ + + "Fear not - you are not the only practicioner of magic in this world. On your travels, you may well encounter a tall tower or two with a distinctive pointed roof. This is the residence of a fellow wizard. A life of solitude can make wizards a little grumpy at times, but they are usually friendly folk who are willing to share their knowledge with you - albeit for a price. Expect to pay precious metals and gems, and in return you will recieve many a great arcane wonder. If, however, it is a master spell you seek, you will need to speak to a specialist.", + + "Unfortunately, there are a few wizards out there who do not welcome visitors. These wizards are typically outcasts, and are hostile to anyone crossing their path. Approach these individuals with caution, and be prepared to defend yourself against powerful magic. Defeat them however, and their knowledge is yours for the taking.", + + "It should also be noted that no wizard will take kindly to being attacked or stolen from - and will readily take the law into their own hands." + ] + }, + "obelisks": { + "title": "Obelisks", + "include_in_contents": "magical_world_subsections", + "text": [ + + "Vestiges of ancient magic are scattered throughout the world, the most notable of which are carved stone structures which bear a great many symbols and runes. These ruins are all that remain of what appears to have been some kind of ancient civilisation. Whoever, or whatever, built them, they clearly had a considerable knowledge of magic, and used it to place protective enchantments over such locations that still persist to this day.", + + "These stuctures fit broadly into two types. The first, and more common, of these are obelisks: tall spikes of carved stone, known as runestone, with an open structure at the bottom that usually contains minor arcane relics from the forgotten past. These structures are typically protected by an enchantment that summons hostile magical creatures should any human stray too close. Fend off these creatures and destroy their source, and the relics are yours." + ] + }, + "shrines": { + "title": "Shrines", + "include_in_contents": "magical_world_subsections", + "text": [ + + "The second, and rarer, kind of structure is known as a shrine. These structures consist of a circle of runestone pillars surrounding a central platform, upon which sits a chest filled with ancient artefacts. This chest is typically protected by an arcane lock enchantment, preventing anyone except the owner from opening it. The entire structure is also protected by a containment field, which prevents anything that strays too close from escaping.", + + "Due to their great arcane power and significance, not to mention the riches within, these structures are particularly attractive to any aspiring wizard - but be cautious. There are numerous reports of wizards becoming trapped within containment fields and slowly being driven insane, perhaps by claustrophobia. A growing number, however, believe such occurences to be a deliberate part of the shrine's protective magic, and that the wizards trapped within are in fact being controlled to protect the structure. A chilling prospect, surely, for anyone who dares to venture near..." + ] + }, + "creatures": { + "title": "Magical Creatures", + "include_in_contents": "magical_world_subsections", + "text": [ + + "Besides wizards, a multitude of other creatures also use magic. Many of these creatures are arcane in origin, and most can be summoned using certain magical spells. One may encounter these creatures guarding an obelisk, or perhaps a powerful summoner. Lesser arcane beings are even found occasionally in the wilderness." + ] + }, + "artefacts": { + "title": "Artefacts", + "include_in_contents": "magical_world_subsections", + "text": [ + + "When exploring a shrine, you may be lucky enough to uncover some kind of ancient magical artefact - an object capable of granting its wearer unique and powerful buffs and special powers. Three types are known to have been found: rings, which grant bonuses and effects to spells, amulets, which improve defensive abilities, and trinkets, which give utility effects. These artefacts appear to function only when worn appropriately; simply having them on one's person is insufficient." + ] + } + } + }, + + "tiers": { + "title": "Tiers", + "include_in_contents": "main_contents", + "text":[ + + "Wands and spells come in four tiers: #colour_novicenovice#colour_reset, #colour_apprenticeapprentice#colour_reset, #colour_advancedadvanced #colour_resetand #colour_mastermaster#colour_reset. Each tier is more powerful than the last.", + + "#colour_noviceNovice #colour_resetwands are the ones that can be crafted. They hold up to #novice_max_charge mana, and can only cast novice spells. Novice spells are simple enough to be cast by anyone and they do not usually cost much mana. This does not necessarily mean that they are useless at higher tiers however; their low cost makes some novice spells useful even when you have access to master spells.", + + "#colour_apprenticeApprentice #colour_resetwands are the next tier up from novice wands. They hold up to #apprentice_max_charge mana and can cast novice and apprentice spells. Apprentice spells are a little more impressive than novice spells but usually cost more.", + + "#colour_advancedAdvanced #colour_resetwands are quite rare and are much more powerful. They can cast all spells except master spells, and hold #advanced_max_charge mana. Advanced spells can grant superhuman powers such as invisibility and wreak havoc on enemies.", + + "#colour_masterMaster #colour_resetwands are the most powerful wands in existence. They hold up to #master_max_charge mana, and can cast any spell. Master spell books are very rare and the spells can cause complete devastation not only to enemies but even the world itself. Use with caution." + ] + }, + + "elements": { + "title": "Elements", + "include_in_contents": "main_contents", + "text": [ + + "Spells belong to different elements, which describe the nature of the spell and also have perks when used with certain wands.", + + "#colour_fireFire#colour_reset \nPerhaps the most destructive element, fire concerns burning, lava, and explosions. A powerful pyromancer will unleash hell upon their enemies and probably set the world alight in the process. Most fire attack spells set fire to their target, causing ongoing damage over time. However, be wary that fire spells may have no effect on nether mobs.", + + "#colour_iceIce#colour_reset \nThe element of ice is made of all that is cold. Frost spells often slow enemies down or freeze them completely, and are especially effective on creatures of fire. Frost magic can prove equally useful outside of combat, such as freezing water to cross a river.", + + "#colour_lightningLightning#colour_reset \nThis element concerns lightning, storms, and the weather. A powerful storm mage is a force to be reckoned with, and some posess the ability to call down lightning at will. Lightning spells often damage multiple enemies at once and usually seek their target, making them an effective attack against any mob... but beware of creepers.", + + "#colour_necromancyNecromancy#colour_reset \nNecromancy is the element of darkness, chaos and the undead. Necromancers are mysterious and often regarded as evil, though this is usually not the case. Necromancy spells are commonly used to summon creatures to fight for you or even bend the will of your enemies.", + + "#colour_earthEarth#colour_reset \nThe element of earth is focused on the natural world: animals, plants, the wind, and such like. Earth magic is diverse and takes a wide variety of forms, from poisoning enemies to unleashing the fury of the weather. Earth spells are a mixture of attack, defence and utility.", + + "#colour_sorcerySorcery#colour_reset \nSorcery is the element of force and change. Sorcerers manipulate light, gravity and even reality itself to fit their needs. Sorcery spells can be used, among other things, to grant their caster magical powers or move objects to their will.", + + "#colour_healingHealing#colour_reset \nThe element of healing is concerned with defence and regeneration. Healers seek to protect themselves and their allies as much as possible and as such can be very difficult opponents. Though generally not used as an attack, the purifying light of some healing spells will do considerable damage to the undead. Healing spells are indispensable when in combat.", + + "The boundaries between the different elements are not always distinct, and some spells bear traits of elements other than their own.", + + "If you are lucky, you may happen upon an elemental wand. These wands will allow you to cast spells of the right element for more potency than usual." + ] + }, + + "growing_crystals": { + "title": "Growing Crystals", + "include_in_contents": "main_contents", + "text": [ + + "Magic crystals normally form naturally underground over thousands of years, by gradually drawing latent mana from the surrounding rock as they grow. Crystalline structures are very good at absorbing and holding mana, hence the mana is naturally drawn towards them. Given the right conditions, however, it is possible to greatly speed up this process. This has given rise to the advanced technique of crystal growing, which requires both great patience and attention to detail.", + + "In order to encourage crystal growth, one must begin with a crystal seed. The most effective seeds have proven to be crystal shards, obtained by simply shattering a magic crystal into pieces. A crystal seed must then be planted into some kind of substrate, usually rock, although other materials are also viable. Left alone, this would produce a sizeable crystal in hundreds of years, depending on the conditions.", + + "To reduce this length of time, several things can be done. Immersing the seed and substrate in mineral-rich water helps with crystal growth, but not with concentrating mana. To concentrate mana more quickly, two techniques can be employed: firstly, surrounding the growth site with lesser mana-gathering objects allows the crystal to draw from them, meaning the surrounding environment is constantly replenished with mana. Too many, however, and they begin to draw mana away from the growing crystal. Secondly, mana can be drawn through some materials better than others: air is very poor at this, whereas dense materials tend to be much better. Some wizards have taken to experimentation with exotic, magical, or even otherworldy materials to try and improve crystal growth. Some even claim to have altered the type of crystal that is formed in this way.", + + "If conditions are exactly right, it is even possible for crystals to grow far beyond their natural size. Such crystals are known as grand magic crystals, and besides containing a great quantity of mana, they have a few specific uses." + ] + }, + + "miscellaneous": { + "title": "Miscellaneous", + "include_in_contents": "main_contents", + "contents": { + "id": "miscellaneous_subsections", + "hyperlinks": true, + "page_numbers": true, + "separator": "." + }, + "sections": { + "mana_flasks": { + "title": "Mana Flasks", + "include_in_contents": "miscellaneous_subsections", + "text": [ + + "Arkendur's Arcane Supplies Co. - magical items for your every need!", + + "Need to recharge your wand on the go? No problem! Mana can now be bottled.** Simply craft a mana flask with a glass bottle and eight magic crystals and take it with you. When you need to use it, simply craft it with your wand and it will restore some of the charge.*", + + "NEW! Introducing the brand-new large*** and small mana flasks - now you can choose a size of mana flask to meet your needs!", + + "* This process will consume the bottle.", + "** Some mana is lost during bottling.", + "*** Large mana flask requires grand magic crystals." + ] + }, + "throwable_items": { + "title": "Throwable Items", + "include_in_contents": "miscellaneous_subsections", + "text": [ + + "Various spells conjure physical items, some of which can also be crafted directly. Firebombs, poison bombs, spark bombs and smoke bombs can all be crafted." + ] + }, + "automated_casting": { + "title": "Automated Casting", + "include_in_contents": "miscellaneous_subsections", + "text": [ + + "Recent experimentation has revealed that it is possible to automate spell casting, to a degree, using nothing more than a simple dispenser. Nobody is quite sure why, but it would appear that the strange properties of redstone even extend to triggering the activation of spell scrolls. Placing a few into a dispenser and powering it ought to do the trick..." + ] + } + } + }, + + "crafting_recipes": { + "title": "Crafting Recipes", + "include_in_contents": "main_contents", + "text": [ + + "#recipe arcane_workbench", + "#recipe magic_wand", + "#recipe magic_missile_spell_book", + "#recipe wizard_handbook", + "#recipe crystal_flower_to_crystals", + "#recipe mana_flask", + "#recipe transportation_stone", + "#recipe magic_silk", + "#recipe wizard_hat", + "#recipe wizard_robe", + "#recipe wizard_leggings", + "#recipe wizard_boots", + "#recipe blank_scroll", + "#recipe firebomb", + "#recipe poison_bomb", + "#recipe smoke_bomb" + ] + }, + + "credits": { + "title": "Credits", + "include_in_contents": "main_contents", + "text": [ + + "Electroblob's Wizardry \nVersion #version \nFor Minecraft #mcversion", + + "For more information, check out the @https://github.com/Electroblob77/Wizardry/wiki wiki@.", + + "Designed, coded and textured by Electroblob", + + "Thanks to Minecraft Forge and MCP, without which this mod would not have been possible.", + + "Thanks also to the Minecraft modding community, which always has an answer to my modding problems!", + + "In addition, I'd like to thank the following individuals for their contributions to the mod:", + + "Code:", + + "- Corail31 \n- 12foo \n- Shadows-of-Fire", + + "Translations:", + + "- Russian: VilagVil \n- Spanish and Mexican Spanish: MadWrist \n- Chinese: ZHENGLOC", + + "Lightning ray sound effect from OhhWowProductions" + ] + } + + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/texts/handbook_en_us.json b/src/main/resources/assets/ebwizardry/texts/handbook_en_us.json new file mode 100644 index 00000000..4c40250d --- /dev/null +++ b/src/main/resources/assets/ebwizardry/texts/handbook_en_us.json @@ -0,0 +1,528 @@ +{ + "ruler_text": "--------------------", + + "colours": { + "text": "#000000", + "caption": "#666666", + "hyperlink": "#601ba0", + "highlight": "#dd4c1d" + }, + + "images": { + "workbench": { + "location": "ebwizardry:textures/gui/arcane_workbench_picture.png", + "caption": "The Arcane Workbench", + "u": 0, + "v": 0, + "width": 110, + "height": 110 + }, + "crystal_ore": { + "location": "ebwizardry:textures/gui/ore_picture.png", + "caption": "Crystal Ore", + "u": 0, + "v": 0, + "width": 64, + "height": 64 + }, + "magic_crystal": { + "location": "ebwizardry:textures/items/crystal_magic.png", + "caption": "A Magic Crystal", + "u": 6, + "v": 6, + "width": 36, + "height": 33, + "texture_width": 48, + "texture_height": 48 + }, + "magic_wand": { + "location": "ebwizardry:textures/items/wand_basic.png", + "caption": "A Magic Wand", + "u": 0, + "v": 0, + "width": 64, + "height": 64 + }, + "crystal_flower": { + "location": "ebwizardry:textures/gui/flower_picture.png", + "caption": "A Crystal Flower", + "u": 0, + "v": 0, + "width": 64, + "height": 64 + } + }, + + "recipes": { + "arcane_workbench": { + "location": "ebwizardry:arcane_workbench" + }, + "magic_wand": { + "location": "ebwizardry:magic_wand" + }, + "magic_missile_spell_book": { + "location": "ebwizardry:magic_missile_spell_book" + }, + "wizard_handbook": { + "location": "ebwizardry:wizard_handbook" + }, + "crystal_flower_to_crystals": { + "location": "ebwizardry:crystal_flower_to_crystals" + }, + "mana_flask": { + "location": "ebwizardry:mana_flask" + }, + "transportation_stone": { + "location": "ebwizardry:transportation_stone" + }, + "magic_silk": { + "location": "ebwizardry:magic_silk" + }, + "wizard_hat": { + "location": "ebwizardry:wizard_hat" + }, + "wizard_robe": { + "location": "ebwizardry:wizard_robe" + }, + "wizard_leggings": { + "location": "ebwizardry:wizard_leggings" + }, + "wizard_boots": { + "location": "ebwizardry:wizard_boots" + }, + "blank_scroll": { + "location": "ebwizardry:blank_scroll" + }, + "firebomb": { + "location": "ebwizardry:firebomb" + }, + "poison_bomb": { + "location": "ebwizardry:poison_bomb" + }, + "smoke_bomb": { + "location": "ebwizardry:smoke_bomb" + } + }, + + "sections": { + + "inside_cover": { + "centre": { + "x": true, + "y": true + }, + "text": [ + + "The Wizard's Handbook", + + "By Electroblob" + ] + }, + + "introduction": { + "title": "Introduction", + "text": [ + + "Greetings, wizard! This book explains the many ways of the arcane and how to use them. This is no ordinary book, though - its pages will materialize before you as you discover more about the magical world.", + + "Use the arrow buttons to turn between pages, and the double-arrow buttons to quickly flip between sections. Use the central menu button to return to the main contents page.", + + "Click on any purple link text to jump straight to the relevant page. Right-click the bookmark to move it to the current page, and left-click to return to the bookmarked page." + ] + }, + + "main_contents": { + "title": "Contents", + "contents": { + "id": "main_contents", + "hyperlinks": true, + "page_numbers": true, + "separator": "." + } + }, + + "setting_up": { + "title": "Setting Up", + "include_in_contents": "main_contents", + "text": [ + + "To get started with wizardry, you will need a few things:", + + "- A magic wand, crafted with a gold nugget, a stick, and a magic crystal.", + + "- An @arcane_workbench arcane workbench@, crafted with 3 stone, 1 lapis lazuli block, 2 magic crystals, 2 gold nuggets and 1 purple carpet.", + + "- A @spells spell@ book. These can be found in chests, dropped as loot, purchased from wizards, or you can make a beginner's spell, magic missile, with a book and 4 magic crystals.", + + "You will also need a bunch more magic crystals to supply your wand with @mana@." + ] + }, + + "mana": { + "title": "Mana", + "include_in_contents": "main_contents", + "triggers": [ + "ebwizardry:crystal" + ], + "text": [ + + "Mana is the arcane energy that gives wizards their powers. It is not a physical substance in its own right; rather, it is an everpresent aura which permeates everything in the world. In particular, it manifests itself in naturally occurring crystals, which can be found embedded in rocks underground. @wands Wands@ can store mana within them, and this mana is channeled into the @spells@ that you cast. Different spells require different amounts of mana, depending on how powerful they are and how long they last.", + + "#image crystal_ore", + + "#image magic_crystal", + + "It is widely accepted that mana cannot be created or destroyed, and that when a spell is cast, the mana it channels is simply dissipated into the surroundings. Indeed, this is how the vast majority of mana exists - spread thinly throughout the world. However, to be of any use, it must be concentrated, either naturally over thousands of years, as is the case with crystals underground, or artificially - an advanced subject covered later in this book. There also exist various ways of making spells more mana-efficient and recovering some of the mana dissipated during spellcasting." + ] + }, + + "wands": { + "title": "Wands", + "include_in_contents": "main_contents", + "triggers": [ + "ebwizardry:arcane_initiate" + ], + "text": [ + + "The wand is the implement of choice for a wizard. With it, you can cast any @spells spell@ provided the wand can contain its power (see @tiers Tiers@). Wands come in many different varieties but you will almost certainly start with a basic magic wand.", + + "#image magic_wand", + + "Most wands begin their life as a simple arrangement of a magic crystal of some sort attached to a wooden stick, with a gold nugget affixed to the other end. The crystal is, of course, the source of the wand's power, as it provides the focus necessary to channel @spells@. The rest of the wand is important too though, as it provides not only a means with which to hold the wand, but also a path through which @mana@ can be channeled and directed.", + + "As more spells are cast with a wand, it grows more effective at channeling @spells@, and can therefore cast spells of greater power. This effect can be discerned from subtle changes that occur in the shape and appearance of the wand itself: as a wand grows more powerful, its crystal will become more vibrant, it may change in color depending on its element, and after a while, the wood from which it is made will start to grow and twist into more complex shapes.", + + "When holding a wand, a small heads-up display will show in the corner of the screen. This indicates the spell that is currently selected along with a pictorial representation of it, and, if the spell has been cast, a cooldown bar indicating the time until that spell can be cast again. It also shows the names of the next and previous spells bound to the wand. To switch between spells, use the #next_spell_key and #previous_spell_key keys (these can be changed in options -> controls). You can also switch spells by scrolling the mouse wheel while sneaking.", + + "When viewing an inventory, hovering over a wand will display how much @mana@ is stored in it, along with its currently selected @spells spell@ and any specific abilities it may have. More in-depth information about a wand can be viewed by placing it in an @arcane_workbench arcane workbench@." + ] + }, + + "spells": { + "title": "Spells", + "include_in_contents": "main_contents", + "triggers": [ + "ebwizardry:handbook/spells" + ], + "text": [ + + "A @wands wand@ is useless without spells to cast with it. Spells are recorded in two forms: in books, which are permanent, and in scrolls, which are temporary.", + + "Spell books are of little use on their own; instead they are used to bind the spell they contain to @wands@. Spell books can be found throughout the world and it will not take long for you to come across one. When you do find one, you can mouse over the spell book to see basic information about the spell at a glance. You can also right-click whilst holding a spell book to read more detailed information about the spell.", + + "Scrolls, on the other hand, serve a different purpose: right-clicking whilst holding one will cast the spell that is bound to it, destroying the scroll in the process. Like spell books, scrolls can be found throughout the world, but you can also make them yourself by crafting a blank scroll from a piece of paper and some string. You can then bind a spell to the scroll using the @arcane_workbench arcane workbench@, but only if you have knowledge of that spell (see @enchanting_scrolls Enchanting Scrolls@). Scrolls are quite useful when you need to use a spell once or twice on a particular quest but don't want it to take up a slot on your @wands wand@. It should be noted, however, that scrolls are at best an inefficient method of casting spells.", + + "To the untrained eye, the magical runes with which spells are written are unreadable. In order to identify a spell, you could, of course, cast it and see what happens - though doing so may cause unwanted side-effects, and many spells need certain conditions in order to work. A safer and more reliable option is to use a scroll of identification, which can be found in chests or purchased from a wizard. Right-clicking whilst holding one will identify the first unknown spell book or scroll on your hotbar, consuming the scroll of identification in the process." + ] + }, + + "arcane_workbench": { + "title": "Arcane Workbench", + "include_in_contents": "main_contents", + "contents": { + "id": "arcane_workbench_subsections", + "hyperlinks": true, + "page_numbers": true, + "separator": "." + }, + "triggers": [ + "ebwizardry:handbook/arcane_workbench" + ], + "text": [ + + "The arcane workbench is a block similar in appearance to the enchantment table where you can charge, upgrade, and bind @spells@ to your @wands wand@. Simply place it anywhere and right click, and you will see something like this:", + + "#image workbench" + ], + "sections": { + "binding_spells": { + "title": "Binding Spells", + "include_in_contents": "arcane_workbench_subsections", + "text": [ + + "To bind a spell to your wand, simply place your wand in the central slot of the workbench, then place the spell book in one of the surrounding slots and press apply. You will notice that the spell book is left untouched during this process - this is because the act of spell binding does not 'take' the spell from the spell book; rather, it attunes the wand to the spell. As such, you may change the spells bound to your wand as much as you wish by simply repeating the spell binding process. You can bind up to five spells to a wand at one time, though this number may be increased with attunement upgrades." + ] + }, + "charging_wands": { + "title": "Charging Wands", + "include_in_contents": "arcane_workbench_subsections", + "text": [ + + "To charge your wand, place the wand in the central slot of the arcane workbench and place some magic crystals in the upper of the two slots on the left, and then press apply. Each crystal is worth #mana_per_crystal mana, and the wand will only take what it needs, so you can keep a supply of crystals in the workbench for when you need them. However, bear in mind that the wand can only take a whole number of crystals so if the wand needs, for example, 30 more mana, #mana_per_crystal_minus_30 mana will be lost when charging it." + ] + }, + "upgrading_wands": { + "title": "Upgrading Wands", + "include_in_contents": "arcane_workbench_subsections", + "text": [ + + "To upgrade your wand, you will need a tome of arcana of the appropriate tier (see @tiers Tiers@) or a special wand upgrade. Both of these can be found as loot or purchased from a wizard. To upgrade your wand, place it in the central slot and the upgrade in the lower of the two slots on the left, then press apply.", + + "Special wand upgrades improve a particular aspect of a wand. Each type of upgrade can stack up to three times, and the total number of upgrades that a wand can take depends on its tier. Upgrades cannot be removed." + ] + }, + "enchanting_scrolls": { + "title": "Enchanting Scrolls", + "include_in_contents": "arcane_workbench_subsections", + "text": [ + + "The arcane workbench can also be used to enchant spell scrolls. To do so, you will require a blank scroll, crafted from a piece of paper and some string, a spell book for your chosen spell, and enough magic crystals to provide mana to cast it. Place the crystals in the upper left-hand slot, the blank scroll in the central slot, and the spell book in the single slot that appears above it, then press apply to enchant the scroll." + ] + } + } + }, + + "wizard_armour": { + "title": "Wizard Armor", + "include_in_contents": "main_contents", + "text": [ + + "As a wizard, you will need something to protect you from the creatures you fight. No ordinary armor will do though. To make the most of your spells, you will need wizard armor: hat, robes, leggings and boots. Unlike ordinary armor, these will not break. Instead, they use arcane energy to shield the wearer. This of course means that they must be charged with magic crystals, just like wands. Fortunately, these garments do not consume much mana. Charge them as you would a wand in the arcane workbench or with a mana flask, but be careful - if they run out of mana, you will find yourself defenseless.", + + "You can obtain wizard armor by crafting it from magical silk, obtained by crafting string with a magic crystal.", + + "Those wizards who devote themselves to the practise of one element sometimes wear special garments which grant bonuses for that element. Full sets of such garments are very sought after among wizards and are not easy to find.", + + "Legend speaks of an arcane seal used by the most powerful wizards to greatly improve their armor." + ] + }, + + "magical_world": { + "title": "Magic in the World", + "include_in_contents": "main_contents", + "contents": { + "id": "magical_world_subsections", + "hyperlinks": true, + "page_numbers": true, + "separator": "." + }, + "text": [ + + "One cannot master the arcane just by staying at home - there is a world full of magic out there to explore! Ancient ruins, relics and mysterious beings good and bad await discovery - if you know where to look. The greatest wizards are also the most curious, and gain much of their knowledge and power through exploration of the environment around them, and experimenting with what they find.", + + "Whilst much of a wizard's time is inevitably spent alone, the sharing of knowledge is also vital to learning the arcane arts. Seek out fellow practicioners of magic, and learn as much as you can from them: communication is arguably the greatest power of all." + ], + "sections": { + "crystal_flowers": { + "title": "Crystal Flowers", + "include_in_contents": "magical_world_subsections", + "triggers": [ + "ebwizardry:handbook/crystal_flowers" + ], + "text": [ + + "During your travels, you will probably come across curious glowing flowers growing in the wild from time to time. These distinctive blooms are known as crystal flowers, and they are strangely effective at concentrating mana - to this day, nobody is quite sure why. We do know, however, that they can be harvested and crafted to extract the mana as crystals, making them a rather useful above-ground source of mana. The amount of mana obtainable this way is limited, though, by the small size of the flowers and due to them only growing in small patches.", + + "#image crystal_flower" + ] + }, + "wizard_towers": { + "title": "Wizard Towers", + "include_in_contents": "magical_world_subsections", + "text": [ + + "Fear not - you are not the only practicioner of magic in this world. On your travels, you may well encounter a tall tower or two with a distinctive pointed roof. This is the residence of a fellow wizard. A life of solitude can make wizards a little grumpy at times, but they are usually friendly folk who are willing to share their knowledge with you - albeit for a price. Expect to pay precious metals and gems, and in return you will recieve many a great arcane wonder. If, however, it is a master spell you seek, you will need to speak to a specialist.", + + "Unfortunately, there are a few wizards out there who do not welcome visitors. These wizards are typically outcasts, and are hostile to anyone crossing their path. Approach these individuals with caution, and be prepared to defend yourself against powerful magic. Defeat them however, and their knowledge is yours for the taking.", + + "It should also be noted that no wizard will take kindly to being attacked or stolen from - and will readily take the law into their own hands." + ] + }, + "obelisks": { + "title": "Obelisks", + "include_in_contents": "magical_world_subsections", + "text": [ + + "Vestiges of ancient magic are scattered throughout the world, the most notable of which are carved stone structures which bear a great many symbols and runes. These ruins are all that remain of what appears to have been some kind of ancient civilisation. Whoever, or whatever, built them, they clearly had a considerable knowledge of magic, and used it to place protective enchantments over such locations that still persist to this day.", + + "These stuctures fit broadly into two types. The first, and more common, of these are obelisks: tall spikes of carved stone, known as runestone, with an open structure at the bottom that usually contains minor arcane relics from the forgotten past. These structures are typically protected by an enchantment that summons hostile magical creatures should any human stray too close. Fend off these creatures and destroy their source, and the relics are yours." + ] + }, + "shrines": { + "title": "Shrines", + "include_in_contents": "magical_world_subsections", + "text": [ + + "The second, and rarer, kind of structure is known as a shrine. These structures consist of a circle of runestone pillars surrounding a central platform, upon which sits a chest filled with ancient artifacts. This chest is typically protected by an arcane lock enchantment, preventing anyone except the owner from opening it. The entire structure is also protected by a containment field, which prevents anything that strays too close from escaping.", + + "Due to their great arcane power and significance, not to mention the riches within, these structures are particularly attractive to any aspiring wizard - but be cautious. There are numerous reports of wizards becoming trapped within containment fields and slowly being driven insane, perhaps by claustrophobia. A growing number, however, believe such occurences to be a deliberate part of the shrine's protective magic, and that the wizards trapped within are in fact being controlled to protect the structure. A chilling prospect, surely, for anyone who dares to venture near..." + ] + }, + "creatures": { + "title": "Magical Creatures", + "include_in_contents": "magical_world_subsections", + "text": [ + + "Besides wizards, a multitude of other creatures also use magic. Many of these creatures are arcane in origin, and most can be summoned using certain magical spells. One may encounter these creatures guarding an obelisk, or perhaps a powerful summoner. Lesser arcane beings are even found occasionally in the wilderness." + ] + }, + "artefacts": { + "title": "Artifacts", + "include_in_contents": "magical_world_subsections", + "text": [ + + "When exploring a shrine, you may be lucky enough to uncover some kind of ancient magical artifact - an object capable of granting its wearer unique and powerful buffs and special powers. Three types are known to have been found: rings, which grant bonuses and effects to spells, amulets, which improve defensive abilities, and trinkets, which give utility effects. These artifacts appear to function only when worn appropriately; simply having them on one's person is insufficient." + ] + } + } + }, + + "tiers": { + "title": "Tiers", + "include_in_contents": "main_contents", + "text":[ + + "Wands and spells come in four tiers: #colour_novicenovice#colour_reset, #colour_apprenticeapprentice#colour_reset, #colour_advancedadvanced #colour_resetand #colour_mastermaster#colour_reset. Each tier is more powerful than the last.", + + "#colour_noviceNovice #colour_resetwands are the ones that can be crafted. They hold up to #novice_max_charge mana, and can only cast novice spells. Novice spells are simple enough to be cast by anyone and they do not usually cost much mana. This does not necessarily mean that they are useless at higher tiers however; their low cost makes some novice spells useful even when you have access to master spells.", + + "#colour_apprenticeApprentice #colour_resetwands are the next tier up from novice wands. They hold up to #apprentice_max_charge mana and can cast novice and apprentice spells. Apprentice spells are a little more impressive than novice spells but usually cost more.", + + "#colour_advancedAdvanced #colour_resetwands are quite rare and are much more powerful. They can cast all spells except master spells, and hold #advanced_max_charge mana. Advanced spells can grant superhuman powers such as invisibility and wreak havoc on enemies.", + + "#colour_masterMaster #colour_resetwands are the most powerful wands in existence. They hold up to #master_max_charge mana, and can cast any spell. Master spell books are very rare and the spells can cause complete devastation not only to enemies but even the world itself. Use with caution." + ] + }, + + "elements": { + "title": "Elements", + "include_in_contents": "main_contents", + "text": [ + + "Spells belong to different elements, which describe the nature of the spell and also have perks when used with certain wands.", + + "#colour_fireFire#colour_reset \nPerhaps the most destructive element, fire concerns burning, lava, and explosions. A powerful pyromancer will unleash hell upon their enemies and probably set the world alight in the process. Most fire attack spells set fire to their target, causing ongoing damage over time. However, be wary that fire spells may have no effect on nether mobs.", + + "#colour_iceIce#colour_reset \nThe element of ice is made of all that is cold. Frost spells often slow enemies down or freeze them completely, and are especially effective on creatures of fire. Frost magic can prove equally useful outside of combat, such as freezing water to cross a river.", + + "#colour_lightningLightning#colour_reset \nThis element concerns lightning, storms, and the weather. A powerful storm mage is a force to be reckoned with, and some posess the ability to call down lightning at will. Lightning spells often damage multiple enemies at once and usually seek their target, making them an effective attack against any mob... but beware of creepers.", + + "#colour_necromancyNecromancy#colour_reset \nNecromancy is the element of darkness, chaos and the undead. Necromancers are mysterious and often regarded as evil, though this is usually not the case. Necromancy spells are commonly used to summon creatures to fight for you or even bend the will of your enemies.", + + "#colour_earthEarth#colour_reset \nThe element of earth is focused on the natural world: animals, plants, the wind, and such like. Earth magic is diverse and takes a wide variety of forms, from poisoning enemies to unleashing the fury of the weather. Earth spells are a mixture of attack, defense and utility.", + + "#colour_sorcerySorcery#colour_reset \nSorcery is the element of force and change. Sorcerers manipulate light, gravity and even reality itself to fit their needs. Sorcery spells can be used, among other things, to grant their caster magical powers or move objects to their will.", + + "#colour_healingHealing#colour_reset \nThe element of healing is concerned with defense and regeneration. Healers seek to protect themselves and their allies as much as possible and as such can be very difficult opponents. Though generally not used as an attack, the purifying light of some healing spells will do considerable damage to the undead. Healing spells are indispensable when in combat.", + + "The boundaries between the different elements are not always distinct, and some spells bear traits of elements other than their own.", + + "If you are lucky, you may happen upon an elemental wand. These wands will allow you to cast spells of the right element for more potency than usual." + ] + }, + + "growing_crystals": { + "title": "Growing Crystals", + "include_in_contents": "main_contents", + "text": [ + + "Magic crystals normally form naturally underground over thousands of years, by gradually drawing latent mana from the surrounding rock as they grow. Crystalline structures are very good at absorbing and holding mana, hence the mana is naturally drawn towards them. Given the right conditions, however, it is possible to greatly speed up this process. This has given rise to the advanced technique of crystal growing, which requires both great patience and attention to detail.", + + "In order to encourage crystal growth, one must begin with a crystal seed. The most effective seeds have proven to be crystal shards, obtained by simply shattering a magic crystal into pieces. A crystal seed must then be planted into some kind of substrate, usually rock, although other materials are also viable. Left alone, this would produce a sizeable crystal in hundreds of years, depending on the conditions.", + + "To reduce this length of time, several things can be done. Immersing the seed and substrate in mineral-rich water helps with crystal growth, but not with concentrating mana. To concentrate mana more quickly, two techniques can be employed: firstly, surrounding the growth site with lesser mana-gathering objects allows the crystal to draw from them, meaning the surrounding environment is constantly replenished with mana. Too many, however, and they begin to draw mana away from the growing crystal. Secondly, mana can be drawn through some materials better than others: air is very poor at this, whereas dense materials tend to be much better. Some wizards have taken to experimentation with exotic, magical, or even otherworldy materials to try and improve crystal growth. Some even claim to have altered the type of crystal that is formed in this way.", + + "If conditions are exactly right, it is even possible for crystals to grow far beyond their natural size. Such crystals are known as grand magic crystals, and besides containing a great quantity of mana, they have a few specific uses." + ] + }, + + "miscellaneous": { + "title": "Miscellaneous", + "include_in_contents": "main_contents", + "contents": { + "id": "miscellaneous_subsections", + "hyperlinks": true, + "page_numbers": true, + "separator": "." + }, + "sections": { + "mana_flasks": { + "title": "Mana Flasks", + "include_in_contents": "miscellaneous_subsections", + "text": [ + + "Arkendur's Arcane Supplies Co. - magical items for your every need!", + + "Need to recharge your wand on the go? No problem! Mana can now be bottled.** Simply craft a mana flask with a glass bottle and eight magic crystals and take it with you. When you need to use it, simply craft it with your wand and it will restore some of the charge.*", + + "NEW! Introducing the brand-new large*** and small mana flasks - now you can choose a size of mana flask to meet your needs!", + + "* This process will consume the bottle.", + "** Some mana is lost during bottling.", + "*** Large mana flask requires grand magic crystals." + ] + }, + "throwable_items": { + "title": "Throwable Items", + "include_in_contents": "miscellaneous_subsections", + "text": [ + + "Various spells conjure physical items, some of which can also be crafted directly. Firebombs, poison bombs, spark bombs and smoke bombs can all be crafted." + ] + }, + "automated_casting": { + "title": "Automated Casting", + "include_in_contents": "miscellaneous_subsections", + "text": [ + + "Recent experimentation has revealed that it is possible to automate spell casting, to a degree, using nothing more than a simple dispenser. Nobody is quite sure why, but it would appear that the strange properties of redstone even extend to triggering the activation of spell scrolls. Placing a few into a dispenser and powering it ought to do the trick..." + ] + } + } + }, + + "crafting_recipes": { + "title": "Crafting Recipes", + "include_in_contents": "main_contents", + "text": [ + + "#recipe arcane_workbench", + "#recipe magic_wand", + "#recipe magic_missile_spell_book", + "#recipe wizard_handbook", + "#recipe crystal_flower_to_crystals", + "#recipe mana_flask", + "#recipe transportation_stone", + "#recipe magic_silk", + "#recipe wizard_hat", + "#recipe wizard_robe", + "#recipe wizard_leggings", + "#recipe wizard_boots", + "#recipe blank_scroll", + "#recipe firebomb", + "#recipe poison_bomb", + "#recipe smoke_bomb" + ] + }, + + "credits": { + "title": "Credits", + "include_in_contents": "main_contents", + "text": [ + + "Electroblob's Wizardry \nVersion #version \nFor Minecraft #mcversion", + + "For more information, check out the @https://github.com/Electroblob77/Wizardry/wiki wiki@.", + + "Designed, coded and textured by Electroblob", + + "Thanks to Minecraft Forge and MCP, without which this mod would not have been possible.", + + "Thanks also to the Minecraft modding community, which always has an answer to my modding problems!", + + "In addition, I'd like to thank the following individuals for their contributions to the mod:", + + "Code:", + + "- Corail31 \n- 12foo \n- Shadows-of-Fire", + + "Translations:", + + "- Russian: VilagVil \n- Spanish and Mexican Spanish: MadWrist \n- Chinese: ZHENGLOC", + + "Lightning ray sound effect from OhhWowProductions" + ] + } + + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/textures/gui/arcane_workbench_picture.png b/src/main/resources/assets/ebwizardry/textures/gui/arcane_workbench_picture.png new file mode 100644 index 0000000000000000000000000000000000000000..7587aba0fedbe91dd8553f928d65a5d49193297b GIT binary patch literal 15386 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/ebwizardry/textures/gui/flower_picture.png b/src/main/resources/assets/ebwizardry/textures/gui/flower_picture.png new file mode 100644 index 0000000000000000000000000000000000000000..d00b9a498e0ffd20644fd1bad376e0f4097ab356 GIT binary patch literal 86005 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

  • L3 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/ebwizardry/textures/gui/handbook_recipes.png b/src/main/resources/assets/ebwizardry/textures/gui/handbook_recipes.png deleted file mode 100644 index 6aab80dcc20a1dde179b209f7f56dafe0d7a3c6c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26539 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 S4{re7nd9l|=d#Wzp$P!d0Xl~O