diff --git a/core/src/main/assets/messages/scenes/scenes.properties b/core/src/main/assets/messages/scenes/scenes.properties index 0ed249ba9..230850d73 100644 --- a/core/src/main/assets/messages/scenes/scenes.properties +++ b/core/src/main/assets/messages/scenes/scenes.properties @@ -4,6 +4,12 @@ scenes.alchemyscene.title=Alchemy scenes.alchemyscene.text=Combine ingredients to create something new! scenes.alchemyscene.select=Select an item scenes.alchemyscene.energy=Energy: +scenes.alchemyscene.add=Add Item +scenes.alchemyscene.no_items=There are no alchemizeable items in that bag. +scenes.alchemyscene.craft=Craft +scenes.alchemyscene.energize=Energize Items +scenes.alchemyscene.cancel=Cancel Ingredients +scenes.alchemyscene.repeat=Repeat Ingredients scenes.amuletscene.exit=Let's call it a day scenes.amuletscene.stay=I'm not done yet diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/Item.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/Item.java index 992bc07d1..23de71b8f 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/Item.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/Item.java @@ -293,6 +293,17 @@ public class Item implements Bundlable { return split; } } + + public Item duplicate(){ + Item dupe = Reflection.newInstance(getClass()); + if (dupe == null){ + return null; + } + Bundle copy = new Bundle(); + this.storeInBundle(copy); + dupe.restoreFromBundle(copy); + return dupe; + } public final Item detach( Bag container ) { 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 12f51c3fd..a217740ad 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.SPDAction; import com.shatteredpixel.shatteredpixeldungeon.ShatteredPixelDungeon; import com.shatteredpixel.shatteredpixeldungeon.Statistics; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Belongings; @@ -34,24 +35,33 @@ import com.shatteredpixel.shatteredpixeldungeon.items.Item; import com.shatteredpixel.shatteredpixeldungeon.items.LiquidMetal; import com.shatteredpixel.shatteredpixeldungeon.items.Recipe; import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.AlchemistsToolkit; +import com.shatteredpixel.shatteredpixeldungeon.items.bags.Bag; import com.shatteredpixel.shatteredpixeldungeon.journal.Document; import com.shatteredpixel.shatteredpixeldungeon.journal.Journal; import com.shatteredpixel.shatteredpixeldungeon.messages.Messages; import com.shatteredpixel.shatteredpixeldungeon.sprites.ItemSprite; import com.shatteredpixel.shatteredpixeldungeon.sprites.ItemSpriteSheet; +import com.shatteredpixel.shatteredpixeldungeon.ui.Button; import com.shatteredpixel.shatteredpixeldungeon.ui.ExitButton; import com.shatteredpixel.shatteredpixeldungeon.ui.IconButton; import com.shatteredpixel.shatteredpixeldungeon.ui.Icons; import com.shatteredpixel.shatteredpixeldungeon.ui.ItemSlot; +import com.shatteredpixel.shatteredpixeldungeon.ui.RadialMenu; import com.shatteredpixel.shatteredpixeldungeon.ui.RedButton; import com.shatteredpixel.shatteredpixeldungeon.ui.RenderedTextBlock; +import com.shatteredpixel.shatteredpixeldungeon.ui.Toolbar; import com.shatteredpixel.shatteredpixeldungeon.ui.Window; import com.shatteredpixel.shatteredpixeldungeon.windows.WndBag; import com.shatteredpixel.shatteredpixeldungeon.windows.WndEnergizeItem; import com.shatteredpixel.shatteredpixeldungeon.windows.WndInfoItem; import com.shatteredpixel.shatteredpixeldungeon.windows.WndJournal; +import com.shatteredpixel.shatteredpixeldungeon.windows.WndKeyBindings; +import com.shatteredpixel.shatteredpixeldungeon.windows.WndMessage; import com.watabou.gltextures.TextureCache; import com.watabou.glwrap.Blending; +import com.watabou.input.ControllerHandler; +import com.watabou.input.GameAction; +import com.watabou.input.KeyBindings; import com.watabou.noosa.Camera; import com.watabou.noosa.Game; import com.watabou.noosa.Image; @@ -72,7 +82,12 @@ public class AlchemyScene extends PixelScene { private static final InputButton[] inputs = new InputButton[3]; private static final CombineButton[] combines = new CombineButton[3]; private static final OutputSlot[] outputs = new OutputSlot[3]; - + + private IconButton cancel; + private IconButton repeat; + private static ArrayList lastIngredients = new ArrayList<>(); + private static Recipe lastRecipe = null; + private Emitter smokeEmitter; private Emitter bubbleEmitter; private Emitter sparkEmitter; @@ -121,6 +136,15 @@ public class AlchemyScene extends PixelScene { im.scale.y = Camera.main.width; add(im); + ExitButton btnExit = new ExitButton(){ + @Override + protected void onClick() { + Game.switchScene(GameScene.class); + } + }; + btnExit.setPos( Camera.main.width - btnExit.width(), 0 ); + add( btnExit ); + bubbleEmitter = new Emitter(); bubbleEmitter.pos(0, 0, Camera.main.width, Camera.main.height); bubbleEmitter.autoKill = false; @@ -168,6 +192,143 @@ public class AlchemyScene extends PixelScene { } } + Button invSelector = new Button(){ + @Override + protected void onClick() { + if (Dungeon.hero != null) { + ArrayList bags = Dungeon.hero.belongings.getBags(); + + String[] names = new String[bags.size()]; + Image[] images = new Image[bags.size()]; + for (int i = 0; i < bags.size(); i++){ + names[i] = Messages.titleCase(bags.get(i).name()); + images[i] = new ItemSprite(bags.get(i)); + } + String info = ""; + if (ControllerHandler.controllerActive){ + info += KeyBindings.getKeyName(KeyBindings.getFirstKeyForAction(GameAction.LEFT_CLICK, true)) + ": " + Messages.get(Toolbar.class, "container_select") + "\n"; + info += KeyBindings.getKeyName(KeyBindings.getFirstKeyForAction(GameAction.BACK, true)) + ": " + Messages.get(Toolbar.class, "container_cancel"); + } else { + info += Messages.get(WndKeyBindings.class, SPDAction.LEFT_CLICK.name()) + ": " + Messages.get(Toolbar.class, "container_select") + "\n"; + info += KeyBindings.getKeyName(KeyBindings.getFirstKeyForAction(GameAction.BACK, false)) + ": " + Messages.get(Toolbar.class, "container_cancel"); + } + + Game.scene().addToFront(new RadialMenu(Messages.get(Toolbar.class, "container_prompt"), info, names, images){ + @Override + public void onSelect(int idx, boolean alt) { + super.onSelect(idx, alt); + Bag bag = bags.get(idx); + ArrayList items = (ArrayList) bag.items.clone(); + + for(Item i : bag.items){ + if (Dungeon.hero.belongings.lostInventory() && !i.keptThroughLostInventory()) items.remove(i); + if (!Recipe.usableInRecipe(i)) items.remove(i); + } + + if (items.size() == 0){ + ShatteredPixelDungeon.scene().addToFront(new WndMessage(Messages.get(AlchemyScene.class, "no_items"))); + return; + } + + String[] itemNames = new String[items.size()]; + Image[] itemIcons = new Image[items.size()]; + for (int i = 0; i < items.size(); i++){ + itemNames[i] = Messages.titleCase(items.get(i).name()); + itemIcons[i] = new ItemSprite(items.get(i)); + } + + String info = ""; + if (ControllerHandler.controllerActive){ + info += KeyBindings.getKeyName(KeyBindings.getFirstKeyForAction(GameAction.LEFT_CLICK, true)) + ": " + Messages.get(Toolbar.class, "item_select") + "\n"; + info += KeyBindings.getKeyName(KeyBindings.getFirstKeyForAction(GameAction.BACK, true)) + ": " + Messages.get(Toolbar.class, "item_cancel"); + } else { + info += Messages.get(WndKeyBindings.class, SPDAction.LEFT_CLICK.name()) + ": " + Messages.get(Toolbar.class, "item_select") + "\n"; + info += KeyBindings.getKeyName(KeyBindings.getFirstKeyForAction(GameAction.BACK, false)) + ": " + Messages.get(Toolbar.class, "item_cancel"); + } + + Game.scene().addToFront(new RadialMenu(Messages.get(Toolbar.class, "item_prompt"), info, itemNames, itemIcons){ + @Override + public void onSelect(int idx, boolean alt) { + super.onSelect(idx, alt); + Item item = items.get(idx); + synchronized (inputs) { + if (item != null && inputs[0] != null) { + for (int i = 0; i < inputs.length; i++) { + if (inputs[i].item() == null) { + if (item instanceof LiquidMetal){ + inputs[i].item(item.detachAll(Dungeon.hero.belongings.backpack)); + } else { + inputs[i].item(item.detach(Dungeon.hero.belongings.backpack)); + } + break; + } + } + updateState(); + } + } + + } + }); + } + }); + } + } + + @Override + public GameAction keyAction() { + return SPDAction.INVENTORY_SELECTOR; + } + }; + add(invSelector); + + cancel = new IconButton(Icons.CLOSE.get()){ + @Override + protected void onClick() { + super.onClick(); + clearSlots(); + updateState(); + } + + @Override + public GameAction keyAction() { + return SPDAction.BACK; + } + + @Override + protected String hoverText() { + return Messages.get(AlchemyScene.class, "cancel"); + } + }; + cancel.setRect(left + 8, pos + 2, 16, 16); + cancel.enable(false); + add(cancel); + + repeat = new IconButton(Icons.REPEAT.get()){ + @Override + protected void onClick() { + super.onClick(); + if (lastRecipe != null){ + populate(lastIngredients, Dungeon.hero.belongings); + } + } + + @Override + public GameAction keyAction() { + return SPDAction.TAG_RESUME; + } + + @Override + protected String hoverText() { + return Messages.get(AlchemyScene.class, "repeat"); + } + }; + repeat.setRect(left + 24, pos + 2, 16, 16); + repeat.enable(false); + add(repeat); + + lastIngredients.clear(); + lastRecipe = null; + for (int i = 0; i < inputs.length; i++){ combines[i] = new CombineButton(i); combines[i].enable(false); @@ -197,16 +358,7 @@ public class AlchemyScene extends PixelScene { lowerBubbles.pos(0, pos, Camera.main.width, Math.max(0, Camera.main.height-pos)); lowerBubbles.pour(Speck.factory( Speck.BUBBLE ), 0.1f ); - - ExitButton btnExit = new ExitButton(){ - @Override - protected void onClick() { - Game.switchScene(GameScene.class); - } - }; - btnExit.setPos( Camera.main.width - btnExit.width(), 0 ); - add( btnExit ); - + IconButton btnGuide = new IconButton( new ItemSprite(ItemSpriteSheet.ALCH_PAGE, null)){ @Override protected void onClick() { @@ -231,6 +383,11 @@ public class AlchemyScene extends PixelScene { }); } + @Override + public GameAction keyAction() { + return SPDAction.JOURNAL; + } + @Override protected String hoverText() { return Messages.titleCase(Document.ALCHEMY_GUIDE.title()); @@ -263,6 +420,16 @@ public class AlchemyScene extends PixelScene { protected void onClick() { WndEnergizeItem.openItemSelector(); } + + @Override + public GameAction keyAction() { + return SPDAction.TAG_ACTION; + } + + @Override + protected String hoverText() { + return Messages.get(AlchemyScene.class, "energize"); + } }; energyAdd.setRect(energyLeft.right(), energyLeft.top() - (16 - energyLeft.height())/2, 16, 16); align(energyAdd); @@ -339,7 +506,9 @@ public class AlchemyScene extends PixelScene { } private void updateState(){ - + + repeat.enable(false); + ArrayList ingredients = filterInput(Item.class); ArrayList recipes = Recipe.findRecipes(ingredients); @@ -354,6 +523,8 @@ public class AlchemyScene extends PixelScene { } } + cancel.enable(!ingredients.isEmpty()); + if (recipes.isEmpty()){ combines[0].setPos(combines[0].left(), inputs[1].top()+5); outputs[0].setPos(outputs[0].left(), inputs[1].top()); @@ -397,6 +568,11 @@ public class AlchemyScene extends PixelScene { ArrayList ingredients = filterInput(Item.class); if (ingredients.isEmpty()) return; + lastIngredients.clear(); + for (Item i : ingredients){ + lastIngredients.add(i.duplicate()); + } + ArrayList recipes = Recipe.findRecipes(ingredients); if (recipes.size() <= slot) return; @@ -467,7 +643,19 @@ public class AlchemyScene extends PixelScene { result.quantity(resultQuantity); outputs[0].item(result); } - + + boolean foundItems = true; + for (Item i : lastIngredients){ + Item found = Dungeon.hero.belongings.getSimilar(i); + if (found == null){ //atm no quantity check as items are always loaded individually + //currently found can be true if we need, say, 3x of an item but only have 2x of it + foundItems = false; + } + } + + lastRecipe = recipe; + repeat.enable(foundItems); + cancel.enable(false); } public void populate(ArrayList toFind, Belongings inventory){ @@ -526,6 +714,8 @@ public class AlchemyScene extends PixelScene { } } } + cancel.enable(false); + repeat.enable(lastRecipe != null); } public void createEnergy(){ @@ -599,6 +789,34 @@ public class AlchemyScene extends PixelScene { } return false; } + + @Override + //only the first empty button accepts key input + public GameAction keyAction() { + for (InputButton i : inputs){ + if (i.item == null || i.item instanceof WndBag.Placeholder) { + if (i == InputButton.this) { + return SPDAction.INVENTORY; + } else { + return super.keyAction(); + } + } + } + return super.keyAction(); + } + + @Override + protected String hoverText() { + if (item == null || item instanceof WndBag.Placeholder){ + return Messages.get(AlchemyScene.class, "add"); + } + return super.hoverText(); + } + + @Override + public GameAction secondaryTooltipAction() { + return SPDAction.INVENTORY_SELECTOR; + } }; slot.enable(true); add( slot ); @@ -652,6 +870,19 @@ public class AlchemyScene extends PixelScene { super.onClick(); combine(slot); } + + @Override + protected String hoverText() { + return "Craft"; + } + + @Override + public GameAction keyAction() { + if (slot == 0 && !combines[1].active && !combines[2].active){ + return SPDAction.TAG_LOOT; + } + return super.keyAction(); + } }; button.icon(Icons.get(Icons.ARROW)); add(button); @@ -696,6 +927,7 @@ public class AlchemyScene extends PixelScene { } layout(); + active = enabled; } }