diff --git a/core/src/main/assets/interfaces/hero_icons.png b/core/src/main/assets/interfaces/hero_icons.png index 8f53c3168..60a5bdfc4 100644 Binary files a/core/src/main/assets/interfaces/hero_icons.png and b/core/src/main/assets/interfaces/hero_icons.png differ diff --git a/core/src/main/assets/messages/items/items.properties b/core/src/main/assets/messages/items/items.properties index 1d5a55650..9fb6de458 100644 --- a/core/src/main/assets/messages/items/items.properties +++ b/core/src/main/assets/messages/items/items.properties @@ -343,6 +343,7 @@ items.artifacts.etherealchains$chainsrecharge.levelup=Your chains grow stronger! items.artifacts.holytome.name=holy tome items.artifacts.holytome.ac_cast=CAST items.artifacts.holytome.no_charge=Your tome is out of charge. Gain exp to recharge it. +items.artifacts.holytome.no_spell=You're not able to cast that spell right now. items.artifacts.holytome.levelup=Your tome grows stronger! items.artifacts.holytome.desc=A holy tome that acts as a focus for the Cleric's divine magic. Using the tome lets the Cleric cast a variety of magical spells.\n\nThe tome will steadily get more powerful as the Cleric uses it, and it will recharge as the Cleric gains experience points when defeating enemies. diff --git a/core/src/main/assets/messages/windows/windows.properties b/core/src/main/assets/messages/windows/windows.properties index f39058bf7..ea59c6294 100644 --- a/core/src/main/assets/messages/windows/windows.properties +++ b/core/src/main/assets/messages/windows/windows.properties @@ -41,8 +41,12 @@ windows.wndclass.mastery=Mastery windows.wndclericspells.cast_title=cast a spell windows.wndclericspells.info_title=spell info -windows.wndclericspells.cast_desc=Select a spell to cast it, or press the info button to switch to info mode. +windows.wndclericspells.cast_desc_desktop=Select a spell to cast it, right click for info or to set a spell to quick cast. +windows.wndclericspells.cast_desc_mobile=Select a spell to cast it, long press to set a spell to quick cast, or press the info button to switch to info mode. windows.wndclericspells.info_desc=Select a spell to learn about it, or press the info button to switch to cast mode. +windows.wndclericspells.cast=Cast +windows.wndclericspells.info=Info +windows.wndclericspells.quick_cast=Quick Cast windows.wndcombo.title=choose a combo move windows.wndcombo.combo_req=(%d combo) diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/spells/ClericSpell.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/spells/ClericSpell.java index f02edb31b..374abde05 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/spells/ClericSpell.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/spells/ClericSpell.java @@ -119,6 +119,19 @@ public abstract class ClericSpell { } return spells; - }; + } + public static ArrayList getAllSpells() { + ArrayList spells = new ArrayList<>(); + spells.add(GuidingLight.INSTANCE); + spells.add(HolyWeapon.INSTANCE); + spells.add(HolyWard.INSTANCE); + spells.add(ShieldOfLight.INSTANCE); + spells.add(DetectCurse.INSTANCE); + spells.add(Sunray.INSTANCE); + spells.add(RecallGlyph.INSTANCE); + spells.add(DivineSense.INSTANCE); + spells.add(HolyLance.INSTANCE); + return spells; + } } diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/spells/GuidingLight.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/spells/GuidingLight.java index eee50a3e0..d3d783368 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/spells/GuidingLight.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/spells/GuidingLight.java @@ -86,7 +86,9 @@ public class GuidingLight extends TargetedClericSpell { hero.next(); onSpellCast(tome, hero); - Buff.affect(hero, GuidingLightPriestCooldown.class, 100f); + if (hero.subClass == HeroSubClass.PRIEST) { + Buff.affect(hero, GuidingLightPriestCooldown.class, 100f); + } } }); diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/artifacts/HolyTome.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/artifacts/HolyTome.java index f41bd7259..a5c3d8c29 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/artifacts/HolyTome.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/artifacts/HolyTome.java @@ -26,6 +26,8 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.MagicImmune; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Talent; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.spells.ClericSpell; +import com.shatteredpixel.shatteredpixeldungeon.actors.hero.spells.DetectCurse; +import com.shatteredpixel.shatteredpixeldungeon.actors.hero.spells.TargetedClericSpell; import com.shatteredpixel.shatteredpixeldungeon.items.Item; import com.shatteredpixel.shatteredpixeldungeon.items.bags.Bag; import com.shatteredpixel.shatteredpixeldungeon.items.rings.RingOfEnergy; @@ -33,8 +35,12 @@ import com.shatteredpixel.shatteredpixeldungeon.journal.Catalog; import com.shatteredpixel.shatteredpixeldungeon.messages.Messages; import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene; import com.shatteredpixel.shatteredpixeldungeon.sprites.ItemSpriteSheet; +import com.shatteredpixel.shatteredpixeldungeon.ui.ActionIndicator; +import com.shatteredpixel.shatteredpixeldungeon.ui.HeroIcon; +import com.shatteredpixel.shatteredpixeldungeon.ui.QuickSlotButton; import com.shatteredpixel.shatteredpixeldungeon.utils.GLog; import com.shatteredpixel.shatteredpixeldungeon.windows.WndClericSpells; +import com.watabou.utils.Bundle; import java.util.ArrayList; @@ -203,7 +209,45 @@ public class HolyTome extends Artifact { } } - public class TomeRecharge extends ArtifactBuff { + private ClericSpell quickSpell = null; + + public void setQuickSpell(ClericSpell spell){ + quickSpell = spell; + if (passiveBuff != null){ + ActionIndicator.setAction((ActionIndicator.Action) passiveBuff); + } + } + + private static final String QUICK_CLS = "quick_cls"; + + @Override + public void storeInBundle(Bundle bundle) { + super.storeInBundle(bundle); + if (quickSpell != null) { + bundle.put(QUICK_CLS, quickSpell.getClass()); + } + } + + @Override + public void restoreFromBundle(Bundle bundle) { + super.restoreFromBundle(bundle); + if (bundle.contains(QUICK_CLS)){ + Class quickCls = bundle.getClass(QUICK_CLS); + for (ClericSpell spell : ClericSpell.getAllSpells()){ + if (spell.getClass() == quickCls){ + quickSpell = spell; + } + } + } + } + + public class TomeRecharge extends ArtifactBuff implements ActionIndicator.Action { + + @Override + public void fx(boolean on) { + if (on && quickSpell != null) ActionIndicator.setAction(this); + else ActionIndicator.clearAction(this); + } public void gainCharge(float levelPortion) { if (cursed || target.buff(MagicImmune.class) != null) return; @@ -242,6 +286,51 @@ public class HolyTome extends Artifact { } } + @Override + public String actionName() { + return quickSpell.name(); + } + + @Override + public int actionIcon() { + return quickSpell.icon() + HeroIcon.SPELL_ACTION_OFFSET; + } + + @Override + public int indicatorColor() { + if (quickSpell == DetectCurse.INSTANCE){ + return 0x00A0FF; + } else { + return 0x002157; + } + } + + @Override + public void doAction() { + if (!canCast(Dungeon.hero, quickSpell)){ + GLog.w(Messages.get(HolyTome.this, "no_spell")); + return; + } + + //TODO this is all pretty akward, might be better to just add autotarget functionality to the indicator + if (QuickSlotButton.targetingSlot != -1 && + Dungeon.quickslot.getItem(QuickSlotButton.targetingSlot) == HolyTome.this) { + int cell = QuickSlotButton.autoAim(QuickSlotButton.lastTarget, HolyTome.this); + + if (cell != -1){ + GameScene.handleCell(cell); + } else { + //couldn't auto-aim, just target the position and hope for the best. + GameScene.handleCell( QuickSlotButton.lastTarget.pos ); + } + } else { + quickSpell.onCast(HolyTome.this, Dungeon.hero); + + if (quickSpell instanceof TargetedClericSpell && Dungeon.quickslot.contains(HolyTome.this)){ + QuickSlotButton.useTargeting(Dungeon.quickslot.getSlot(HolyTome.this)); + } + } + } } } diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/ui/HeroIcon.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/ui/HeroIcon.java index bcd08f600..309facc6c 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/ui/HeroIcon.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/ui/HeroIcon.java @@ -83,15 +83,17 @@ public class HeroIcon extends Image { public static final int RECALL_GLYPH = 47; public static final int HOLY_LANCE = 48; - //action indicator visuals - public static final int BERSERK = 80; - public static final int COMBO = 81; - public static final int PREPARATION = 82; - public static final int MOMENTUM = 83; - public static final int SNIPERS_MARK = 84; - public static final int WEAPON_SWAP = 85; - public static final int MONK_ABILITIES = 86; + //all cleric spells have a separate icon with no background for the action indicator + public static final int SPELL_ACTION_OFFSET = 32; + //action indicator visuals + public static final int BERSERK = 104; + public static final int COMBO = 105; + public static final int PREPARATION = 106; + public static final int MOMENTUM = 107; + public static final int SNIPERS_MARK = 108; + public static final int WEAPON_SWAP = 109; + public static final int MONK_ABILITIES = 110; public HeroIcon(HeroSubClass subCls){ super( Assets.Interfaces.HERO_ICONS ); diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/windows/WndClericSpells.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/windows/WndClericSpells.java index 70d87d6d0..6dfb573ea 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/windows/WndClericSpells.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/windows/WndClericSpells.java @@ -38,9 +38,15 @@ import com.shatteredpixel.shatteredpixeldungeon.ui.IconButton; import com.shatteredpixel.shatteredpixeldungeon.ui.Icons; import com.shatteredpixel.shatteredpixeldungeon.ui.QuickSlotButton; import com.shatteredpixel.shatteredpixeldungeon.ui.RenderedTextBlock; +import com.shatteredpixel.shatteredpixeldungeon.ui.RightClickMenu; import com.shatteredpixel.shatteredpixeldungeon.ui.Window; +import com.shatteredpixel.shatteredpixeldungeon.utils.GLog; +import com.watabou.input.PointerEvent; import com.watabou.noosa.ColorBlock; +import com.watabou.noosa.Image; import com.watabou.noosa.NinePatch; +import com.watabou.utils.DeviceCompat; +import com.watabou.utils.PointF; import java.util.ArrayList; @@ -67,8 +73,14 @@ public class WndClericSpells extends Window { btnInfo.setRect(WIDTH-16, 0, 16, 16); add(btnInfo); - //TODO we might want to intercept quickslot hotkeys and auto-cast the last spell if relevant - RenderedTextBlock msg = PixelScene.renderTextBlock( Messages.get( this, info ? "info_desc" : "cast_desc"), 6); + RenderedTextBlock msg; + if (info){ + msg = PixelScene.renderTextBlock( Messages.get( this, "info_desc"), 6); + } else if (DeviceCompat.isDesktop()){ + msg = PixelScene.renderTextBlock( Messages.get( this, "cast_desc_desktop"), 6); + } else { + msg = PixelScene.renderTextBlock( Messages.get( this, "cast_desc_mobile"), 6); + } msg.maxWidth(WIDTH); msg.setPos(0, title.bottom()+4); add(msg); @@ -127,8 +139,8 @@ public class WndClericSpells extends Window { this.tome = tome; this.info = info; - if (!info && !tome.canCast(Dungeon.hero, spell)){ - enable(false); + if (!tome.canCast(Dungeon.hero, spell)){ + icon.alpha( 0.3f ); } bg = Chrome.get(Chrome.Type.TOAST); @@ -152,15 +164,73 @@ public class WndClericSpells extends Window { GameScene.show(new WndTitledMessage(new HeroIcon(spell), Messages.titleCase(spell.name()), spell.desc())); } else { hide(); - spell.onCast(tome, Dungeon.hero); - //TODO, probably need targeting logic here - if (spell instanceof TargetedClericSpell && Dungeon.quickslot.contains(tome)){ - QuickSlotButton.useTargeting(Dungeon.quickslot.getSlot(tome)); + + if(!spell.canCast(Dungeon.hero)){ + GLog.w(Messages.get(HolyTome.class, "no_spell")); + } else { + spell.onCast(tome, Dungeon.hero); + + //TODO, probably need targeting logic here + if (spell instanceof TargetedClericSpell && Dungeon.quickslot.contains(tome)){ + QuickSlotButton.useTargeting(Dungeon.quickslot.getSlot(tome)); + } } + } } + @Override + protected boolean onLongClick() { + hide(); + tome.setQuickSpell(spell); + return true; + } + + @Override + protected void onRightClick() { + super.onRightClick(); + RightClickMenu r = new RightClickMenu(new Image(icon), + Messages.titleCase(spell.name()), + Messages.get(WndClericSpells.class, "cast"), + Messages.get(WndClericSpells.class, "info"), + Messages.get(WndClericSpells.class, "quick_cast")){ + @Override + public void onSelect(int index) { + switch (index){ + default: + //do nothing + break; + case 0: + hide(); + if(!spell.canCast(Dungeon.hero)){ + GLog.w(Messages.get(HolyTome.class, "no_spell")); + } else { + spell.onCast(tome, Dungeon.hero); + + //TODO, probably need targeting logic here + if (spell instanceof TargetedClericSpell && Dungeon.quickslot.contains(tome)){ + QuickSlotButton.useTargeting(Dungeon.quickslot.getSlot(tome)); + } + } + break; + case 1: + GameScene.show(new WndTitledMessage(new HeroIcon(spell), Messages.titleCase(spell.name()), spell.desc())); + break; + case 2: + hide(); + tome.setQuickSpell(spell); + break; + } + } + }; + parent.addToFront(r); + r.camera = camera(); + PointF mousePos = PointerEvent.currentHoverPos(); + mousePos = camera.screenToCamera((int)mousePos.x, (int)mousePos.y); + r.setPos(mousePos.x-3, mousePos.y-3); + } + @Override protected String hoverText() { return "_" + Messages.titleCase(spell.name()) + "_\n" + spell.shortDesc();