diff --git a/core/src/main/assets/items.png b/core/src/main/assets/items.png index fa6cc7ce1..8cf15ef35 100644 Binary files a/core/src/main/assets/items.png and b/core/src/main/assets/items.png differ diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/bombs/Bomb.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/bombs/Bomb.java index 18eb13091..5629bfccd 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/bombs/Bomb.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/bombs/Bomb.java @@ -58,6 +58,7 @@ import com.watabou.utils.Random; import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedHashMap; public class Bomb extends Item { @@ -305,34 +306,38 @@ public class Bomb extends Item { public static class EnhanceBomb extends Recipe { - private static final HashMap, Class> validIngredients = new HashMap<>(); + public static final LinkedHashMap, Class> validIngredients = new LinkedHashMap<>(); static { - validIngredients.put(PotionOfLiquidFlame.class, Firebomb.class); validIngredients.put(PotionOfFrost.class, FrostBomb.class); - validIngredients.put(PotionOfHealing.class, RegrowthBomb.class); - validIngredients.put(PotionOfInvisibility.class, Flashbang.class); - - validIngredients.put(ScrollOfRecharging.class, ShockBomb.class); - validIngredients.put(ScrollOfRemoveCurse.class, HolyBomb.class); validIngredients.put(ScrollOfMirrorImage.class, WoollyBomb.class); + + validIngredients.put(PotionOfLiquidFlame.class, Firebomb.class); validIngredients.put(ScrollOfRage.class, Noisemaker.class); + validIngredients.put(PotionOfInvisibility.class, Flashbang.class); + validIngredients.put(ScrollOfRecharging.class, ShockBomb.class); + + validIngredients.put(PotionOfHealing.class, RegrowthBomb.class); + validIngredients.put(ScrollOfRemoveCurse.class, HolyBomb.class); + validIngredients.put(GooBlob.class, ArcaneBomb.class); validIngredients.put(MetalShard.class, ShrapnelBomb.class); } private static final HashMap, Integer> bombCosts = new HashMap<>(); static { - bombCosts.put(Firebomb.class, 4); bombCosts.put(FrostBomb.class, 3); - bombCosts.put(RegrowthBomb.class, 6); - bombCosts.put(Flashbang.class, 5); - - bombCosts.put(ShockBomb.class, 5); - bombCosts.put(HolyBomb.class, 6); bombCosts.put(WoollyBomb.class, 3); + + bombCosts.put(Firebomb.class, 4); bombCosts.put(Noisemaker.class, 4); + bombCosts.put(Flashbang.class, 5); + bombCosts.put(ShockBomb.class, 5); + + bombCosts.put(RegrowthBomb.class, 6); + bombCosts.put(HolyBomb.class, 6); + bombCosts.put(ArcaneBomb.class, 8); bombCosts.put(ShrapnelBomb.class, 8); } diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/food/MysteryMeat.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/food/MysteryMeat.java index 25404c103..fdbffeff3 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/food/MysteryMeat.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/food/MysteryMeat.java @@ -76,7 +76,7 @@ public class MysteryMeat extends Food { public static class PlaceHolder extends MysteryMeat { { - image = ItemSpriteSheet.MEAT_HOLDER; + image = ItemSpriteSheet.FOOD_HOLDER; } @Override diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/journal/Document.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/journal/Document.java index 89accba67..6fa358a18 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/journal/Document.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/journal/Document.java @@ -59,6 +59,10 @@ public enum Document { return pages.containsKey(page) && pages.get(page); } + public boolean hasPage( int pageIdx ){ + return hasPage( pages.keySet().toArray(new String[0])[pageIdx] ); + } + public boolean hasAnyPages(){ for (String p : pages.keySet()){ if (pages.get(p)) { @@ -81,9 +85,17 @@ public enum Document { return Messages.get( this, name() + "." + page + ".title"); } + public String pageTitle( int pageIdx ){ + return pageTitle( pages.keySet().toArray(new String[0])[pageIdx] ); + } + public String pageBody( String page ){ return Messages.get( this, name() + "." + page + ".body"); } + + public String pageBody( int pageIdx ){ + return pageBody( pages.keySet().toArray(new String[0])[pageIdx] ); + } public static final String GUIDE_INTRO_PAGE = "Intro"; public static final String GUIDE_SEARCH_PAGE = "Examining_and_Searching"; @@ -103,17 +115,13 @@ public enum Document { ALCHEMY_GUIDE.pages.put("Potions", false); ALCHEMY_GUIDE.pages.put("Stones", false); ALCHEMY_GUIDE.pages.put("Darts", false); - ALCHEMY_GUIDE.pages.put("Exotics", false); - ALCHEMY_GUIDE.pages.put("Energy", false); - ALCHEMY_GUIDE.pages.put("Food", false); + ALCHEMY_GUIDE.pages.put("Exotic_Potions", false); + ALCHEMY_GUIDE.pages.put("Exotic_Scrolls", false); + ALCHEMY_GUIDE.pages.put("Energy_Food", false); ALCHEMY_GUIDE.pages.put("Bombs", false); - ALCHEMY_GUIDE.pages.put("Combo_Brews", false); - ALCHEMY_GUIDE.pages.put("Heal_Elixirs", false); - ALCHEMY_GUIDE.pages.put("AOE_Brews", false); - ALCHEMY_GUIDE.pages.put("Imbue_Elixirs", false); - ALCHEMY_GUIDE.pages.put("Tele_Spells", false); - ALCHEMY_GUIDE.pages.put("Item_Spells", false); - ALCHEMY_GUIDE.pages.put("Enviro_Spells", false); + ALCHEMY_GUIDE.pages.put("Brews", false); + ALCHEMY_GUIDE.pages.put("Elixirs", false); + ALCHEMY_GUIDE.pages.put("Spells", false); } private static final String DOCUMENTS = "documents"; diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/rooms/special/LaboratoryRoom.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/rooms/special/LaboratoryRoom.java index a1e58de31..6d364b80b 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/rooms/special/LaboratoryRoom.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/rooms/special/LaboratoryRoom.java @@ -84,9 +84,8 @@ public class LaboratoryRoom extends SpecialRoom { } } - //drops a page every room for now - //TODO make pages rarer as players get more, once more alchemy comes out - if(!missingPages.isEmpty()){ + //pages after 5 are always deeper than the sewers + if(!missingPages.isEmpty() && (missingPages.size() > 5 || Dungeon.depth > 5)){ AlchemyPage p = new AlchemyPage(); p.page(missingPages.get(0)); int pos; diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/scenes/AlchemyScene.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/scenes/AlchemyScene.java index 781fe53cb..b5a4c6907 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/scenes/AlchemyScene.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/scenes/AlchemyScene.java @@ -25,6 +25,7 @@ import com.shatteredpixel.shatteredpixeldungeon.Assets; import com.shatteredpixel.shatteredpixeldungeon.Badges; import com.shatteredpixel.shatteredpixeldungeon.Chrome; import com.shatteredpixel.shatteredpixeldungeon.Dungeon; +import com.shatteredpixel.shatteredpixeldungeon.SPDSettings; import com.shatteredpixel.shatteredpixeldungeon.ShatteredPixelDungeon; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Belongings; import com.shatteredpixel.shatteredpixeldungeon.effects.Speck; @@ -32,7 +33,6 @@ import com.shatteredpixel.shatteredpixeldungeon.items.Item; import com.shatteredpixel.shatteredpixeldungeon.items.Recipe; import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.AlchemistsToolkit; import com.shatteredpixel.shatteredpixeldungeon.items.weapon.missiles.darts.Dart; -import com.shatteredpixel.shatteredpixeldungeon.journal.Document; import com.shatteredpixel.shatteredpixeldungeon.journal.Journal; import com.shatteredpixel.shatteredpixeldungeon.messages.Messages; import com.shatteredpixel.shatteredpixeldungeon.sprites.ItemSprite; @@ -45,8 +45,8 @@ import com.shatteredpixel.shatteredpixeldungeon.ui.RedButton; import com.shatteredpixel.shatteredpixeldungeon.ui.RenderedTextMultiline; import com.shatteredpixel.shatteredpixeldungeon.ui.Window; import com.shatteredpixel.shatteredpixeldungeon.windows.WndBag; -import com.shatteredpixel.shatteredpixeldungeon.windows.WndDocument; import com.shatteredpixel.shatteredpixeldungeon.windows.WndInfoItem; +import com.shatteredpixel.shatteredpixeldungeon.windows.WndJournal; import com.watabou.gltextures.TextureCache; import com.watabou.glwrap.Blending; import com.watabou.noosa.Camera; @@ -250,7 +250,23 @@ public class AlchemyScene extends PixelScene { @Override protected void onClick() { super.onClick(); - AlchemyScene.this.addToFront(new WndDocument(Document.ALCHEMY_GUIDE)); + clearSlots(); + updateState(); + AlchemyScene.this.addToFront(new Window(){ + + { + WndJournal.AlchemyTab t = new WndJournal.AlchemyTab(); + if (SPDSettings.landscape()) { + resize(200, 130); + t.setRect(0, 0, 200, 130); + } else { + resize(130, 180); + t.setRect(0, 0, 130, 180); + } + add(t); + } + + }); } }; btnGuide.setRect(0, 0, 16, 16); @@ -450,6 +466,8 @@ public class AlchemyScene extends PixelScene { Dungeon.level.drop(inputs[i].item, Dungeon.hero.pos); } } + inputs[i].item(null); + inputs[i].slot.item(new WndBag.Placeholder(ItemSpriteSheet.SOMETHING)); } } } diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/ItemSprite.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/ItemSprite.java index 8e8683f80..497a16e31 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/ItemSprite.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/ItemSprite.java @@ -81,6 +81,10 @@ public class ItemSprite extends MovieClip { view (item); } + public ItemSprite( int image ){ + this( image, null ); + } + public ItemSprite( int image, Glowing glowing ) { super( Assets.ITEMS ); diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/ItemSpriteSheet.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/ItemSpriteSheet.java index ae1033fa2..e619b8e95 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/ItemSpriteSheet.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/ItemSpriteSheet.java @@ -36,32 +36,40 @@ public class ItemSpriteSheet { } private static final int PLACEHOLDERS = xy(1, 1); //16 slots - //null warning occupies space 0, should only show up if there's a bug. - public static final int NULLWARN = PLACEHOLDERS+0; + //SOMETHING is the default item sprite at position 0. May show up ingame if there are bugs. + public static final int SOMETHING = PLACEHOLDERS+0; public static final int WEAPON_HOLDER = PLACEHOLDERS+1; public static final int ARMOR_HOLDER = PLACEHOLDERS+2; - public static final int WAND_HOLDER = PLACEHOLDERS+3; - public static final int RING_HOLDER = PLACEHOLDERS+4; - public static final int ARTIFACT_HOLDER = PLACEHOLDERS+5; - public static final int POTION_HOLDER = PLACEHOLDERS+6; - public static final int SCROLL_HOLDER = PLACEHOLDERS+7; - public static final int SEED_HOLDER = PLACEHOLDERS+8; - public static final int STONE_HOLDER = PLACEHOLDERS+9; - public static final int MEAT_HOLDER = PLACEHOLDERS+10; - public static final int SOMETHING = PLACEHOLDERS+11; + public static final int MISSILE_HOLDER = PLACEHOLDERS+3; + public static final int WAND_HOLDER = PLACEHOLDERS+4; + public static final int RING_HOLDER = PLACEHOLDERS+5; + public static final int ARTIFACT_HOLDER = PLACEHOLDERS+6; + public static final int FOOD_HOLDER = PLACEHOLDERS+7; + public static final int BOMB_HOLDER = PLACEHOLDERS+8; + public static final int POTION_HOLDER = PLACEHOLDERS+9; + public static final int SCROLL_HOLDER = PLACEHOLDERS+11; + public static final int SEED_HOLDER = PLACEHOLDERS+10; + public static final int STONE_HOLDER = PLACEHOLDERS+12; + public static final int BREW_HOLDER = PLACEHOLDERS+13; + public static final int ELIXIR_HOLDER = PLACEHOLDERS+14; + public static final int SPELL_HOLDER = PLACEHOLDERS+15; static{ - assignItemRect(NULLWARN, 16, 7); + assignItemRect(SOMETHING, 8, 13); assignItemRect(WEAPON_HOLDER, 14, 14); assignItemRect(ARMOR_HOLDER, 14, 12); + assignItemRect(MISSILE_HOLDER, 15, 15); assignItemRect(WAND_HOLDER, 14, 14); assignItemRect(RING_HOLDER, 8, 10); assignItemRect(ARTIFACT_HOLDER, 15, 15); + assignItemRect(FOOD_HOLDER, 15, 11); + assignItemRect(BOMB_HOLDER, 10, 13); assignItemRect(POTION_HOLDER, 12, 14); - assignItemRect(SCROLL_HOLDER, 15, 14); assignItemRect(SEED_HOLDER, 10, 10); + assignItemRect(SCROLL_HOLDER, 15, 14); assignItemRect(STONE_HOLDER, 14, 12); - assignItemRect(MEAT_HOLDER, 15, 11); - assignItemRect(SOMETHING, 8, 13); + assignItemRect(BREW_HOLDER, 10, 14); + assignItemRect(ELIXIR_HOLDER, 10, 14); + assignItemRect(SPELL_HOLDER, 8, 16); } private static final int UNCOLLECTIBLE = xy(1, 2); //16 slots diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/ui/QuickRecipe.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/ui/QuickRecipe.java new file mode 100644 index 000000000..022c8c62b --- /dev/null +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/ui/QuickRecipe.java @@ -0,0 +1,370 @@ +/* + * Pixel Dungeon + * Copyright (C) 2012-2015 Oleg Dolya + * + * Shattered Pixel Dungeon + * Copyright (C) 2014-2018 Evan Debenham + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ + +package com.shatteredpixel.shatteredpixeldungeon.ui; + +import com.shatteredpixel.shatteredpixeldungeon.Dungeon; +import com.shatteredpixel.shatteredpixeldungeon.ShatteredPixelDungeon; +import com.shatteredpixel.shatteredpixeldungeon.items.Generator; +import com.shatteredpixel.shatteredpixeldungeon.items.Item; +import com.shatteredpixel.shatteredpixeldungeon.items.Recipe; +import com.shatteredpixel.shatteredpixeldungeon.items.bombs.Bomb; +import com.shatteredpixel.shatteredpixeldungeon.items.food.Blandfruit; +import com.shatteredpixel.shatteredpixeldungeon.items.food.Feast; +import com.shatteredpixel.shatteredpixeldungeon.items.food.Food; +import com.shatteredpixel.shatteredpixeldungeon.items.food.MysteryMeat; +import com.shatteredpixel.shatteredpixeldungeon.items.food.Pasty; +import com.shatteredpixel.shatteredpixeldungeon.items.food.StewedMeat; +import com.shatteredpixel.shatteredpixeldungeon.items.potions.Potion; +import com.shatteredpixel.shatteredpixeldungeon.items.potions.brews.BlizzardBrew; +import com.shatteredpixel.shatteredpixeldungeon.items.potions.brews.CausticBrew; +import com.shatteredpixel.shatteredpixeldungeon.items.potions.brews.FrigidBrew; +import com.shatteredpixel.shatteredpixeldungeon.items.potions.brews.FrostfireBrew; +import com.shatteredpixel.shatteredpixeldungeon.items.potions.brews.InfernalBrew; +import com.shatteredpixel.shatteredpixeldungeon.items.potions.brews.ShockingBrew; +import com.shatteredpixel.shatteredpixeldungeon.items.potions.brews.WickedBrew; +import com.shatteredpixel.shatteredpixeldungeon.items.potions.elixirs.ElixirOfAquaticRejuvenation; +import com.shatteredpixel.shatteredpixeldungeon.items.potions.elixirs.ElixirOfDragonsBlood; +import com.shatteredpixel.shatteredpixeldungeon.items.potions.elixirs.ElixirOfHoneyedHealing; +import com.shatteredpixel.shatteredpixeldungeon.items.potions.elixirs.ElixirOfIcyTouch; +import com.shatteredpixel.shatteredpixeldungeon.items.potions.elixirs.ElixirOfRestoration; +import com.shatteredpixel.shatteredpixeldungeon.items.potions.elixirs.ElixirOfToxicEssence; +import com.shatteredpixel.shatteredpixeldungeon.items.potions.elixirs.ElixirOfVitality; +import com.shatteredpixel.shatteredpixeldungeon.items.potions.exotic.ExoticPotion; +import com.shatteredpixel.shatteredpixeldungeon.items.scrolls.Scroll; +import com.shatteredpixel.shatteredpixeldungeon.items.scrolls.exotic.ExoticScroll; +import com.shatteredpixel.shatteredpixeldungeon.items.spells.Alchemize; +import com.shatteredpixel.shatteredpixeldungeon.items.spells.AquaBlast; +import com.shatteredpixel.shatteredpixeldungeon.items.spells.BeaconOfReturning; +import com.shatteredpixel.shatteredpixeldungeon.items.spells.CurseInfusion; +import com.shatteredpixel.shatteredpixeldungeon.items.spells.FeatherFall; +import com.shatteredpixel.shatteredpixeldungeon.items.spells.MagicalInfusion; +import com.shatteredpixel.shatteredpixeldungeon.items.spells.MagicalPorter; +import com.shatteredpixel.shatteredpixeldungeon.items.spells.PhaseShift; +import com.shatteredpixel.shatteredpixeldungeon.items.spells.ReclaimTrap; +import com.shatteredpixel.shatteredpixeldungeon.items.spells.Recycle; +import com.shatteredpixel.shatteredpixeldungeon.items.stones.Runestone; +import com.shatteredpixel.shatteredpixeldungeon.items.weapon.missiles.darts.Dart; +import com.shatteredpixel.shatteredpixeldungeon.items.weapon.missiles.darts.TippedDart; +import com.shatteredpixel.shatteredpixeldungeon.messages.Messages; +import com.shatteredpixel.shatteredpixeldungeon.plants.Plant; +import com.shatteredpixel.shatteredpixeldungeon.scenes.AlchemyScene; +import com.shatteredpixel.shatteredpixeldungeon.scenes.PixelScene; +import com.shatteredpixel.shatteredpixeldungeon.sprites.ItemSpriteSheet; +import com.shatteredpixel.shatteredpixeldungeon.windows.WndBag; +import com.shatteredpixel.shatteredpixeldungeon.windows.WndInfoItem; +import com.watabou.noosa.BitmapText; +import com.watabou.noosa.Group; +import com.watabou.noosa.Image; +import com.watabou.noosa.ui.Component; + +import java.util.ArrayList; +import java.util.Arrays; + +public class QuickRecipe extends Component { + + private ArrayList ingredients; + + private ArrayList inputs; + private QuickRecipe.arrow arrow; + private ItemSlot output; + + public QuickRecipe(Recipe.SimpleRecipe r){ + this(r, r.getIngredients(), r.sampleOutput(null)); + } + + public QuickRecipe(Recipe r, ArrayList inputs, final Item output) { + + ingredients = inputs; + int cost = r.cost(inputs); + boolean hasInputs = true; + this.inputs = new ArrayList<>(); + for (final Item in : inputs) { + anonymize(in); + ItemSlot curr; + curr = new ItemSlot(in) { + @Override + protected void onClick() { + ShatteredPixelDungeon.scene().addToFront(new WndInfoItem(in)); + } + }; + + ArrayList similar = Dungeon.hero.belongings.getAllSimilar(in); + int quantity = 0; + for (Item sim : similar) { + if (sim.isIdentified()) quantity += sim.quantity(); + } + + if (quantity < in.quantity()) { + curr.icon.alpha(0.3f); + hasInputs = false; + } + curr.showParams(true, false, true); + add(curr); + this.inputs.add(curr); + } + + if (cost > 0) { + arrow = new arrow(Icons.get(Icons.RESUME), cost); + arrow.hardlightText(0x00CCFF); + } else { + arrow = new arrow(Icons.get(Icons.RESUME)); + } + if (hasInputs) { + arrow.icon.tint(1, 1, 0, 1); + if (!(ShatteredPixelDungeon.scene() instanceof AlchemyScene)) { + arrow.enable(false); + } + } else { + arrow.icon.color(0, 0, 0); + arrow.enable(false); + } + add(arrow); + + anonymize(output); + this.output = new ItemSlot(output){ + @Override + protected void onClick() { + ShatteredPixelDungeon.scene().addToFront(new WndInfoItem(output)); + } + }; + this.output.showParams(true, false, true); + add(this.output); + + layout(); + } + + @Override + protected void layout() { + + height = 16; + width = 0; + + for (ItemSlot item : inputs){ + item.setRect(x + width, y, 16, 16); + width += 16; + } + + arrow.setRect(x + width, y, 14, 16); + width += 14; + + output.setRect(x + width, y, 16, 16); + width += 16; + } + + //used to ensure that un-IDed items are not spoiled + private void anonymize(Item item){ + if (item instanceof Potion){ + ((Potion) item).anonymize(); + } else if (item instanceof Scroll){ + ((Scroll) item).anonymize(); + } + } + + public class arrow extends IconButton { + + BitmapText text; + + public arrow(){ + super(); + } + + public arrow( Image icon ){ + super( icon ); + } + + public arrow( Image icon, int count ){ + super( icon ); + text = new BitmapText( Integer.toString(count), PixelScene.pixelFont); + text.measure(); + add(text); + } + + @Override + protected void layout() { + super.layout(); + + if (text != null){ + text.x = x; + text.y = y; + PixelScene.align(text); + } + } + + @Override + protected void onClick() { + super.onClick(); + + //find the window this is inside of and close it + Group parent = this.parent; + while (parent != null){ + if (parent instanceof Window){ + ((Window) parent).hide(); + break; + } else { + parent = parent.parent; + } + } + + ((AlchemyScene)ShatteredPixelDungeon.scene()).populate(ingredients, Dungeon.hero.belongings); + } + + public void hardlightText(int color ){ + if (text != null) text.hardlight(color); + } + } + + //gets recipes for a particular alchemy guide page + //a null entry indicates a break in section + public static ArrayList getRecipes( int pageIdx ){ + ArrayList result = new ArrayList<>(); + switch (pageIdx){ + case 0: default: + result.add(new QuickRecipe( new Potion.SeedToPotion(), new ArrayList<>(Arrays.asList(new Plant.Seed.PlaceHolder().quantity(3))), new WndBag.Placeholder(ItemSpriteSheet.POTION_HOLDER){ + { + name = Messages.get(Potion.SeedToPotion.class, "name"); + } + + @Override + public String info() { + return ""; + } + })); + return result; + case 1: + Recipe r = new Scroll.ScrollToStone(); + for (Class cls : Generator.Category.SCROLL.classes){ + try{ + Scroll scroll = (Scroll) cls.newInstance(); + ArrayList in = new ArrayList(Arrays.asList(scroll)); + result.add(new QuickRecipe( r, in, r.sampleOutput(in))); + } catch (Exception e){ + ShatteredPixelDungeon.reportException(e); + } + } + return result; + case 2: + r = new TippedDart.TipDart(); + for (Class cls : Generator.Category.SEED.classes){ + try{ + Plant.Seed seed = (Plant.Seed) cls.newInstance(); + ArrayList in = new ArrayList<>(Arrays.asList(seed, new Dart())); + result.add(new QuickRecipe( r, in, r.sampleOutput(in))); + } catch (Exception e){ + ShatteredPixelDungeon.reportException(e); + } + } + return result; + case 3: + r = new ExoticPotion.PotionToExotic(); + for (Class cls : Generator.Category.POTION.classes){ + try{ + Potion pot = (Potion) cls.newInstance(); + ArrayList in = new ArrayList<>(Arrays.asList(pot, new Plant.Seed.PlaceHolder().quantity(2))); + result.add(new QuickRecipe( r, in, r.sampleOutput(in))); + } catch (Exception e){ + ShatteredPixelDungeon.reportException(e); + } + } + return result; + case 4: + r = new ExoticScroll.ScrollToExotic(); + for (Class cls : Generator.Category.SCROLL.classes){ + try{ + Scroll scroll = (Scroll) cls.newInstance(); + ArrayList in = new ArrayList<>(Arrays.asList(scroll, new Runestone.PlaceHolder().quantity(2))); + result.add(new QuickRecipe( r, in, r.sampleOutput(in))); + } catch (Exception e){ + ShatteredPixelDungeon.reportException(e); + } + } + return result; + case 5: + result.add(new QuickRecipe( new StewedMeat.oneMeat() )); + result.add(new QuickRecipe( new StewedMeat.twoMeat() )); + result.add(new QuickRecipe( new StewedMeat.threeMeat() )); + result.add(null); + result.add(new QuickRecipe( new Feast.Recipe(), + new ArrayList(Arrays.asList(new Pasty(), new Food(), new MysteryMeat.PlaceHolder())), + new Feast())); + result.add(new QuickRecipe( new Blandfruit.CookFruit(), + new ArrayList<>(Arrays.asList(new Blandfruit(), new Plant.Seed.PlaceHolder())), + new Blandfruit(){ + { + name = "Cooked Blandfruit"; + } + + @Override + public String info() { + return ""; + } + })); //TODO + return result; + case 6: + r = new Bomb.EnhanceBomb(); + for (Class cls : Bomb.EnhanceBomb.validIngredients.keySet()){ + try{ + Item item = (Item) cls.newInstance(); + ArrayList in = new ArrayList(Arrays.asList(new Bomb(), item)); + result.add(new QuickRecipe( r, in, r.sampleOutput(in))); + } catch (Exception e){ + ShatteredPixelDungeon.reportException(e); + } + } + return result; + case 7: + result.add(new QuickRecipe(new WickedBrew.Recipe())); + result.add(new QuickRecipe(new FrigidBrew.Recipe())); + result.add(new QuickRecipe(new FrostfireBrew.Recipe())); + result.add(new QuickRecipe(new CausticBrew.Recipe())); + result.add(null); + result.add(new QuickRecipe(new InfernalBrew.Recipe())); + result.add(new QuickRecipe(new BlizzardBrew.Recipe())); + result.add(new QuickRecipe(new ShockingBrew.Recipe())); + return result; + case 8: + result.add(new QuickRecipe(new ElixirOfRestoration.Recipe())); + result.add(new QuickRecipe(new ElixirOfVitality.Recipe())); + result.add(new QuickRecipe(new ElixirOfHoneyedHealing.Recipe())); + result.add(new QuickRecipe(new ElixirOfAquaticRejuvenation.Recipe())); + result.add(null); + result.add(new QuickRecipe(new ElixirOfDragonsBlood.Recipe())); + result.add(new QuickRecipe(new ElixirOfIcyTouch.Recipe())); + result.add(new QuickRecipe(new ElixirOfToxicEssence.Recipe())); + return result; + case 9: + result.add(new QuickRecipe(new MagicalPorter.Recipe())); + result.add(new QuickRecipe(new PhaseShift.Recipe())); + result.add(new QuickRecipe(new BeaconOfReturning.Recipe())); + result.add(null); + result.add(new QuickRecipe(new AquaBlast.Recipe())); + result.add(new QuickRecipe(new FeatherFall.Recipe())); + result.add(new QuickRecipe(new ReclaimTrap.Recipe())); + result.add(null); + result.add(new QuickRecipe(new MagicalInfusion.Recipe())); + result.add(new QuickRecipe(new CurseInfusion.Recipe())); + result.add(new QuickRecipe(new Alchemize.Recipe())); + result.add(new QuickRecipe(new Recycle.Recipe())); + return result; + } + } + +} diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/windows/WndJournal.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/windows/WndJournal.java index 67fb1877b..395353f0f 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/windows/WndJournal.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/windows/WndJournal.java @@ -37,6 +37,7 @@ import com.shatteredpixel.shatteredpixeldungeon.scenes.PixelScene; import com.shatteredpixel.shatteredpixeldungeon.sprites.ItemSprite; import com.shatteredpixel.shatteredpixeldungeon.sprites.ItemSpriteSheet; import com.shatteredpixel.shatteredpixeldungeon.ui.Icons; +import com.shatteredpixel.shatteredpixeldungeon.ui.QuickRecipe; import com.shatteredpixel.shatteredpixeldungeon.ui.RedButton; import com.shatteredpixel.shatteredpixeldungeon.ui.RenderedTextMultiline; import com.shatteredpixel.shatteredpixeldungeon.ui.ScrollPane; @@ -53,15 +54,16 @@ import java.util.HashMap; //FIXME a lot of cleanup and improvements to do here public class WndJournal extends WndTabbed { - private static final int WIDTH_P = 120; - private static final int HEIGHT_P = 160; + private static final int WIDTH_P = 130; + private static final int HEIGHT_P = 180; - private static final int WIDTH_L = 160; - private static final int HEIGHT_L = 128; + private static final int WIDTH_L = 200; + private static final int HEIGHT_L = 130; private static final int ITEM_HEIGHT = 18; private GuideTab guideTab; + private AlchemyTab alchemyTab; private NotesTab notesTab; private CatalogTab catalogTab; @@ -79,6 +81,10 @@ public class WndJournal extends WndTabbed { guideTab.setRect(0, 0, width, height); guideTab.updateList(); + alchemyTab = new AlchemyTab(); + add(alchemyTab); + alchemyTab.setRect(0, 0, width, height); + notesTab = new NotesTab(); add(notesTab); notesTab.setRect(0, 0, width, height); @@ -97,18 +103,25 @@ public class WndJournal extends WndTabbed { if (value) last_index = 0; } }, + new IconTab( new ItemSprite(ItemSpriteSheet.ALCH_PAGE, null) ) { + protected void select( boolean value ) { + super.select( value ); + alchemyTab.active = alchemyTab.visible = value; + if (value) last_index = 1; + } + }, new IconTab( Icons.get(Icons.DEPTH) ) { protected void select( boolean value ) { super.select( value ); notesTab.active = notesTab.visible = value; - if (value) last_index = 1; + if (value) last_index = 2; } }, new IconTab( new ItemSprite(ItemSpriteSheet.WEAPON_HOLDER, null) ) { protected void select( boolean value ) { super.select( value ); catalogTab.active = catalogTab.visible = value; - if (value) last_index = 2; + if (value) last_index = 3; } } }; @@ -223,7 +236,7 @@ public class WndJournal extends WndTabbed { line.y = pos; content.add(line); - RenderedTextMultiline title = PixelScene.renderMultiline(Messages.get(this, "title"), 9); + RenderedTextMultiline title = PixelScene.renderMultiline(Document.ADVENTURERS_GUIDE.title(), 9); title.hardlight(TITLE_COLOR); title.maxWidth( (int)width() - 2 ); title.setPos( (width() - title.width())/2f, pos + 1 + ((ITEM_HEIGHT) - title.height())/2f); @@ -232,8 +245,8 @@ public class WndJournal extends WndTabbed { pos += Math.max(ITEM_HEIGHT, title.height()); - for (Document doc : Document.values()){ - GuideItem item = new GuideItem( doc ); + for (String page : Document.ADVENTURERS_GUIDE.pages()){ + GuideItem item = new GuideItem( page ); item.setRect( 0, pos, width(), ITEM_HEIGHT ); content.add( item ); @@ -249,18 +262,18 @@ public class WndJournal extends WndTabbed { private static class GuideItem extends ListItem { private boolean found = false; - private Document doc; + private String page; - public GuideItem( Document doc ){ - super( new ItemSprite( doc.pageSprite(), null), - Messages.titleCase( doc.title() ), -1); + public GuideItem( String page ){ + super( new ItemSprite( ItemSpriteSheet.GUIDE_PAGE, null), + Messages.titleCase(Document.ADVENTURERS_GUIDE.pageTitle(page)), -1); - this.doc = doc; - found = doc.hasAnyPages(); + this.page = page; + found = Document.ADVENTURERS_GUIDE.hasPage(page); if (!found) { icon.hardlight( 0.5f, 0.5f, 0.5f); - label.text( Messages.titleCase( "???" )); + label.text( Messages.titleCase(Messages.get( this, "missing" ))); label.hardlight( 0x999999 ); } @@ -268,7 +281,7 @@ public class WndJournal extends WndTabbed { public boolean onClick( float x, float y ) { if (inside( x, y ) && found) { - GameScene.show( new WndDocument(doc)); + GameScene.show( new WndStory( Document.ADVENTURERS_GUIDE.pageBody(page) )); return true; } else { return false; @@ -279,6 +292,129 @@ public class WndJournal extends WndTabbed { } + public static class AlchemyTab extends Component { + + private RedButton[] pageButtons; + private static final int NUM_BUTTONS = 10; + + private static final int spriteIndexes[] = {10, 12, 3, 9, 11, 7, 8, 13, 14, 15}; + + private static int currentPageIdx = -1; + + private IconTitle title; + private RenderedTextMultiline body; + + private ArrayList recipes = new ArrayList<>(); + + @Override + protected void createChildren() { + pageButtons = new RedButton[NUM_BUTTONS]; + for (int i = 0; i < NUM_BUTTONS; i++){ + final int idx = i; + pageButtons[i] = new RedButton( "" ){ + @Override + protected void onClick() { + currentPageIdx = idx; + updateList(); + } + }; + if (Document.ALCHEMY_GUIDE.hasPage(i)) { + pageButtons[i].icon(new ItemSprite(ItemSpriteSheet.SOMETHING + spriteIndexes[i], null)); + } else { + pageButtons[i].icon(new ItemSprite(ItemSpriteSheet.SOMETHING, null)); + pageButtons[i].enable(false); + } + add( pageButtons[i] ); + } + + title = new IconTitle(); + title.icon( new ItemSprite(ItemSpriteSheet.ALCH_PAGE)); + title.visible = false; + add(title); + body = PixelScene.renderMultiline(6); + add(body); + } + + @Override + protected void layout() { + super.layout(); + + int perRow = NUM_BUTTONS / (SPDSettings.landscape() ? 1 : 2); + float buttonWidth = width()/perRow; + + for (int i = 0; i < NUM_BUTTONS; i++) { + pageButtons[i].setRect((i%perRow) * (buttonWidth), (i/perRow) * (ITEM_HEIGHT), + buttonWidth, ITEM_HEIGHT); + PixelScene.align(pageButtons[i]); + } + + updateList(); + } + + private void updateList() { + + for (int i = 0; i < NUM_BUTTONS; i++) { + if (i == currentPageIdx) { + pageButtons[i].icon().color(TITLE_COLOR); + } else { + pageButtons[i].icon().resetColor(); + } + } + + if (currentPageIdx == -1){ + return; + } + + title.visible = true; + title.label(Document.ALCHEMY_GUIDE.pageTitle(currentPageIdx)); + title.setRect(0, pageButtons[NUM_BUTTONS-1].bottom(), width(), 10); + + body.maxWidth((int)width()); + body.text(Document.ALCHEMY_GUIDE.pageBody(currentPageIdx)); + body.setPos(0, title.bottom()); + + for (QuickRecipe r : recipes){ + if (r != null) { + r.killAndErase(); + r.destroy(); + } + } + recipes.clear(); + + ArrayList toAdd = QuickRecipe.getRecipes(currentPageIdx); + + float left; + float top = body.bottom() + 2; + int w; + ArrayList toAddThisRow = new ArrayList<>(); + while (!toAdd.isEmpty()){ + while (!toAdd.isEmpty() && toAdd.get(0) == null){ + top += 2; + toAdd.remove(0); + } + + w = 0; + while(!toAdd.isEmpty() && toAdd.get(0) != null + && w + toAdd.get(0).width() <= width()){ + toAddThisRow.add(toAdd.remove(0)); + w += toAddThisRow.get(0).width(); + } + + float spacing = (width() - w)/(toAddThisRow.size() + 1); + left = spacing; + for (QuickRecipe r : toAddThisRow){ + r.setPos(left, top); + left += r.width() + spacing; + recipes.add(r); + add(r); + } + top += 17; + toAddThisRow.clear(); + } + + } + } + private static class NotesTab extends Component { private ScrollPane list; @@ -363,6 +499,7 @@ public class WndJournal extends WndTabbed { private static int currentItemIdx = 0; + //sprite locations private static final int WEAPON_IDX = 0; private static final int ARMOR_IDX = 1; private static final int WAND_IDX = 2; @@ -371,6 +508,8 @@ public class WndJournal extends WndTabbed { private static final int POTION_IDX = 5; private static final int SCROLL_IDX = 6; + private static final int spriteIndexes[] = {1, 2, 4, 5, 6, 9, 11}; + private ScrollPane list; private ArrayList items = new ArrayList<>(); @@ -387,7 +526,7 @@ public class WndJournal extends WndTabbed { updateList(); } }; - itemButtons[i].icon(new ItemSprite(ItemSpriteSheet.WEAPON_HOLDER + i, null)); + itemButtons[i].icon(new ItemSprite(ItemSpriteSheet.SOMETHING + spriteIndexes[i], null)); add( itemButtons[i] ); } @@ -405,8 +544,6 @@ public class WndJournal extends WndTabbed { add( list ); } - private static final int BUTTON_HEIGHT = 17; - @Override protected void layout() { super.layout(); @@ -415,8 +552,8 @@ public class WndJournal extends WndTabbed { float buttonWidth = width()/perRow; for (int i = 0; i < NUM_BUTTONS; i++) { - itemButtons[i].setRect((i%perRow) * (buttonWidth), (i/perRow) * (BUTTON_HEIGHT + 1), - buttonWidth, BUTTON_HEIGHT); + itemButtons[i].setRect((i%perRow) * (buttonWidth), (i/perRow) * (ITEM_HEIGHT ), + buttonWidth, ITEM_HEIGHT); PixelScene.align(itemButtons[i]); } @@ -512,11 +649,11 @@ public class WndJournal extends WndTabbed { this.seen = seen; if (!seen) { - icon.copy( new ItemSprite( ItemSpriteSheet.WEAPON_HOLDER + currentItemIdx, null) ); + icon.copy( new ItemSprite( ItemSpriteSheet.SOMETHING + spriteIndexes[currentItemIdx], null) ); label.text("???"); label.hardlight( 0x999999 ); } else if (!IDed) { - icon.copy( new ItemSprite( ItemSpriteSheet.WEAPON_HOLDER + currentItemIdx, null) ); + icon.copy( new ItemSprite( ItemSpriteSheet.SOMETHING + spriteIndexes[currentItemIdx], null) ); label.hardlight( 0xCCCCCC ); } diff --git a/core/src/main/resources/com/shatteredpixel/shatteredpixeldungeon/messages/journal/journal.properties b/core/src/main/resources/com/shatteredpixel/shatteredpixeldungeon/messages/journal/journal.properties index 8da1e6a65..c352aa2a6 100644 --- a/core/src/main/resources/com/shatteredpixel/shatteredpixeldungeon/messages/journal/journal.properties +++ b/core/src/main/resources/com/shatteredpixel/shatteredpixeldungeon/messages/journal/journal.properties @@ -22,33 +22,25 @@ journal.document.adventurers_guide.magic.body=Magical attacks cut right through journal.document.alchemy_guide.title=Alchemy Guide journal.document.alchemy_guide.potions.title=Creating Potions -journal.document.alchemy_guide.potions.body=Welcome to Practical Applications of Alchemy!\n\nThis book serves as a recipe reference for hobbyist alchemists and adventurers looking to get their hands dirty.\n\nWe will start with the most iconic alchemy recipe: Place any three seeds into an alchemy pot to brew a potion!\n\nEvery seed type has a potion counterpart. The potion you create may relate to one of the seeds used, but there is a chance it will be random as well. Using multiple of the same type of seed will decrease the chance for a random potion. +journal.document.alchemy_guide.potions.body=Welcome to Practical Applications of Alchemy!\n\nThis book serves as a recipe reference for hobbyist alchemists and adventurers looking to get their hands dirty.\n\nWe will start with the most iconic alchemy recipe: Place any three seeds into an alchemy pot to brew a random potion!\n\nEvery seed has a potion counterpart. The potion you create may relate to one of the seeds used, and using multiple of the same seed will increase the chance for this to occur. journal.document.alchemy_guide.stones.title=Creating Runestones -journal.document.alchemy_guide.stones.body=Mixing a single scroll into an alchemy pot will imbue its magic into two or three rocks within the pot. This creates runestones that correspond to that scroll.\n\nRunestones are a lesser variant of scrolls, just as seeds are a lesser variant of potions. Unlike seeds however, runestones cannot be combined to make a scroll. +journal.document.alchemy_guide.stones.body=Mixing a single scroll into an alchemy pot will imbue its magic into two or three rocks within the pot. This creates runestones that correspond to that scroll. journal.document.alchemy_guide.darts.title=Tipping Darts -journal.document.alchemy_guide.darts.body=A single seed can be combined with one or two darts to tip them. Each type of seed produces its own type of tipped dart, with a unique effect.\n\nTipped darts only last for one use however, and will revert to regular darts when recovered. -journal.document.alchemy_guide.exotics.title=Exotic Potions and Scrolls -journal.document.alchemy_guide.exotics.body=The power of a potion or scroll can be augmented to create a new 'exotic' variant. These exotic items have more powerful effects, but they are often useful in different ways as well.\n\nCombining a potion and any two seeds will produce an exotic potion. Combining a scroll with any two runestones will produce an exotic scroll. -journal.document.alchemy_guide.energy.title=Alchemical Energy -journal.document.alchemy_guide.energy.body=While the recipes we have discussed so far only need their ingredients, some recipes require energy from the alchemy pot itself. This Energy is a limited resource, but can be used to make recipes that are more than the sum of their ingredients. Some recipes will require a small amount of energy, some require a lot.\n\nAll further recipes discussed in this book will make use of Alchemical Energy. -journal.document.alchemy_guide.food.title=Food Recipes -journal.document.alchemy_guide.food.body=Not all recipes involve magical potions or scrolls, some are more traditional.\n\nRaw meat can be stewed in an alchemy pot, and the pot's energy will cleanse the meat of disease. The more meat that is used at once, the greater the efficiency of the recipe.\n\nA raw blandfruit can be combined with a seed to create a cooked blandfruit. The cooked fruit will emulate whatever potion the seed corresponds to.\n\nA pasty, full ration, and a piece of meat (any type will do) can be combined to create a feast! Feasts provide an unparalleled culinary experience, but cost a fair amount of energy. +journal.document.alchemy_guide.darts.body=A single seed can be combined with one or two darts to tip them. Each type of seed produces its own type of tipped dart, with a unique single-use effect. +journal.document.alchemy_guide.exotic_potions.title=Exotic Potions +journal.document.alchemy_guide.exotic_potions.body=The power of a potion can be augmented with two seeds to create a new 'exotic' variant. They have more powerful effects, but are often useful in different ways as well. +journal.document.alchemy_guide.exotic_scrolls.title=Exotic Scrolls +journal.document.alchemy_guide.exotic_scrolls.body=Exotic scrolls can also be made with two runestones and a scroll. They're generally a bit stronger than exotic potions, but stones are also harder to come by. +journal.document.alchemy_guide.energy_food.title=Energy and Food +journal.document.alchemy_guide.energy_food.body=Some recipes require energy from the alchemy pot itself. Energy is used to make recipes that are more than the sum of their ingredients, but it's a limited resource.\n\nNot all energy recipes are especially mystical however. These recipes more resemble traditional cooking than alchemy. journal.document.alchemy_guide.bombs.title=Enhanced Bombs -journal.document.alchemy_guide.bombs.body=A standard black powder bomb can be mixed with a specific item to create an enhanced bomb. The amount of energy needed varies by the item used.\n\nThe following items can produce an enhanced bomb:\n- Potion of Liquid Flame\n- Potion of Frost\n- Potion of Healing\n- Potion of Invisibility\n- Scroll of Recharging\n- Scroll of Remove Curse\n- Scroll of Mirror Image\n- Scroll of Rage\n- Blob of Goo\n- Cursed Metal Shard -journal.document.alchemy_guide.combo_brews.title=Combination Brews -journal.document.alchemy_guide.combo_brews.body=Combination brews are the most simple form of brew, combining the effects of two harmful potions into one.\n\n\nA wicked brew is created by mixing a potion of toxic gas with a potion of paralytic gas.\n\nA frigid brew is created by mixing a potion of frost with a potion of storm clouds.\n\nA frostfire brew is created by mixing a potion of liquid flame with a potion of snap freeze. -journal.document.alchemy_guide.heal_elixirs.title=Healing Elixirs -journal.document.alchemy_guide.heal_elixirs.body=Healing elixirs are also quite simple, combining healing and another effect into one item.\n\n\nAn elixir of restoration is created by mixing a potion of healing with a potion of cleansing.\n\nAn elixir of vitality is created by mixing a potion of healing with a potion of shielding.\n\nAn elixir of honeyed healing is created by mixing a potion of healing with a shattered honeypot.\n\nAn elixir of aquatic rejuvenation is created by mixing a potion of healing and a blob of goo. -journal.document.alchemy_guide.aoe_brews.title=Area of Effect Brews -journal.document.alchemy_guide.aoe_brews.body=Area of effect brews spread a harmful effect over a large area. They are more expensive than combination brews, but also more powerful.\n\n\nAn infernal brew is created by mixing a potion of dragon's breath and a potion of liquid flame.\n\nA blizzard brew is created by mixing a potion of snap freeze and a potion of frost.\n\nA shocking brew is created by mixing a potion of paralytic gas and a potion of storm clouds.\n\nA caustic brew is created by mixing a potion of toxic gas and a blob of goo. -journal.document.alchemy_guide.imbue_elixirs.title=Imbuing Elixirs -journal.document.alchemy_guide.imbue_elixirs.body=Imbuing Elixirs will imbue the drinker with a unique power which may be temporary or permanent. They are more expensive than healing elixirs, but also more powerful.\n\n\nAn elixir of dragon's blood is created by mixing a potion of liquid flame and a potion of purity.\n\nAn elixir of toxic essence is created by mixing a potion of toxic gas and a potion of purity.\n\nAn elixir of icy touch is created by mixing a potion of frost and a potion of purity.\n\nAn elixir of might is created by mixing a potion of strength and a large amount of alchemical energy. -journal.document.alchemy_guide.tele_spells.title=Teleportation Spells -journal.document.alchemy_guide.tele_spells.body=Combining certain ingredients in an alchemy pot will cause magical crystals to precipitate out of the water. The energy in these crystals can be channeled to cast spells! Most spells have multiple uses, but the specific amount varies by spell.\n\nTeleportation spells contain magic that changes the positioning of yourself, enemies, or items in various useful ways.\n\n\nMagical porter is created by mixing a scroll of identification with a merchant's beacon.\n\nPhase shift is created by mixing a scroll of teleportation with a scroll of terror.\n\nBeacon of returning is created by mixing a scroll of passage, a scroll of magic mapping, and a lot of alchemical energy. -journal.document.alchemy_guide.item_spells.title=Item Manipulation Spells -journal.document.alchemy_guide.item_spells.body=Item manipulation spells affect the items in your inventory in a variety of different ways.\n\n\nMagical infusion is created by mixing a scroll of upgrade with a stone of enchantment.\n\nCurse infusion is created by mixing a scroll of remove curse with a cursed metal shard.\n\nAlchemize is created by mixing a scroll of recharging with a potion of liquid flame.\n\nRecycle is created by mixing a scroll of transmutation with a scroll of divination. -journal.document.alchemy_guide.enviro_spells.title=Environmental Spells -journal.document.alchemy_guide.enviro_spells.body=Environmental spells give you new ways to change or interact with the terrain of the dungeon.\n\n\nReclaim trap is created by mixing a scroll of recharging with a cursed metal shard.\n\nAqua blast is created by mixing a scroll of identify with a potion of storm clouds.\n\nFeather fall is created by mixing a scroll of lullaby, a potion of levitation, and a good amount of alchemical energy. +journal.document.alchemy_guide.bombs.body=A standard black powder bomb can be mixed with a specific item to create an enhanced bomb. The amount of energy needed varies by the item used. +journal.document.alchemy_guide.brews.title=Brews +journal.document.alchemy_guide.brews.body=Brews are made from potion-based recipes, and are centered around inflicting harmful effects on your enemies. +journal.document.alchemy_guide.elixirs.title=Elixirs +journal.document.alchemy_guide.elixirs.body=Elixirs are made from potion-based recipes, and focus on healing or boosting your abilities. +journal.document.alchemy_guide.spells.title=Spells +journal.document.alchemy_guide.spells.body=Spells are made from scroll-based recipes, and provide a variety of effects with multiple uses. journal.notes$landmark.well_of_health=well of health journal.notes$landmark.well_of_awareness=well of awareness