From ee2f373e61ed21ec1b8486b3ca8030ef934fb5c9 Mon Sep 17 00:00:00 2001 From: Evan Debenham Date: Wed, 27 Nov 2024 14:15:40 -0500 Subject: [PATCH] v3.0.0: added 'quick cast' functionality for cleric spells --- .../src/main/assets/interfaces/hero_icons.png | Bin 2404 -> 2712 bytes .../assets/messages/items/items.properties | 1 + .../messages/windows/windows.properties | 6 +- .../actors/hero/spells/ClericSpell.java | 15 ++- .../actors/hero/spells/GuidingLight.java | 4 +- .../items/artifacts/HolyTome.java | 91 +++++++++++++++++- .../shatteredpixeldungeon/ui/HeroIcon.java | 18 ++-- .../windows/WndClericSpells.java | 86 +++++++++++++++-- 8 files changed, 201 insertions(+), 20 deletions(-) diff --git a/core/src/main/assets/interfaces/hero_icons.png b/core/src/main/assets/interfaces/hero_icons.png index 8f53c3168ce5e2a72115d57f4b34d7ff7f3c0cf1..60a5bdfc4aaaf319f0d315c6e4ae98dd5505fd90 100644 GIT binary patch delta 2520 zcmYk8>048G0)>A!Hw#N3SXLFAz%ZcV17Q(RgalO7g0j>xph6ptCXiJ_pRv-Y)?#z zzIU7y1p;w*k_5xO7P8aK>73W*tx>Zdl3H%-@ix~0_8YQrNB;A81$77Ua5d^ML7O6! zC;&Ij)90+G0m;AY;qx@+r>PB*y_4Q&R+cFpK0VO74CvB5Q1#029Dtl zhFTJp$h95E2^ts@KJmkDT?1!qw<6Fc$@!xPaoFjB<{vd{CI1NpL-qVmpBxKiPOTIu z4F17`=?@qL;*RFbMUVMCOKI)t{n&}Rbw4VDx-9jA1neH{JRnB+3ZSoXWir+;JoAt# z#DrDU;4Dc+3AN`>97c4}SwBx0r8to!<0S02QKxRo#7DQP4q;MDK2qw*9?F|*DM*Ut zG9NHfKDr@ev;tOj!uv?g>w=u!Vrar0gA2@`$E=kAFWLqpVxVtb z=BpN>MUNtJYU5L59OqN*PUfJz+Du_OZX~KAYNzm>)4t}?E)ZckTy!x962+Bq15EQx z;QibEIpv9Z5=+)5hb*P(f9?C;uv9y>)bg+r7DJMy8j5y)a#GF~hu%{q zq6A%fF;W_1Eu8Ln)g8_4B;mTQxeIPVGZKK=S`|SGi-1|{+2!=_kY_WB)y!-ztmO&} zUcg6g=HOQO0O-iOP1Z9Y8@;|5xs8xT06}h6%#hk~D~4UJfpo~Qfrrkqn2*)Rn0DjT zsG&W`+&3=h`@sCz210R}8BViSKp677dVuDn3{p_K#8wO%F zAxkUp)IT`{Ziwv#c%8;tq(JdO?di}m+o*T?;Y^Q$u#Nb$zerD1l8_m(-1lj_wFLd( z=PNQ=9gKoT{Fy<}{U_doE|)*TLQ4*cI1q@xDLdIRs_ty^(^G~?-+iw7KAv)5PMB=QzF%P^8!D9^a8H>Up* z!)p8K!MuZ>8_3bUukhA09SU!sB+|zDel1mF6>2Xw)q22#HfNa&c+Br#*m#lM& zPyMN@2zH%>mDQA;GARxhK}m(&IDK`FfWdY^>rd1LD5@NqM|BZDTx|-*_opRXQ{l@)bdiM(VOt4Z9%z zRJGI`i^R&I<{-;9@|m!zidKm#rFcv=q*uqU^izI`5@zdT zS6pVIeSnyOSUiHD;&~{U$W%A-D52Zb0{Ci0%+ZD#-$PWTKhPEv4wD<>P<4}{)txCh zR7y)83#G#b=V9&7wi!L_LJZN?1{l@M*i}rd&YLz_f6RwzybQE|=)-YJmfF)ygzN>_ zDcm^QnrSQpIu=1D-iR7DLz?QRj^81O6+Yyzh^}b-=8iV^=kC|Objj;y95o4BTrBza zP#i-R(AfVXdS0@;N>mp74=+o&flDh&U9Jhg^YHZGrSKWnc`83fX=F!GVx!~~n-$TL z`sQx}XDg@1T1Ya5eRB_`_jG@83TqzH)Jm2rePq>C5p|MOfEW{6Pk=iGTs@gFDkEto zNe9I?3`(fGkd)O$DN!@bzLHbU8cGJtU#4o^zaduDWxlDV&T_@(t|TWZvOCvD$)8BZ z&_$_bD6-}m-_)TKnD@S%ha>N)mpMriJB$<^&!At2L}6)NsuKrNxStgbwgAm$w0v=` z1KSlT_{9;|Il~nz+WJaGgNg!^u(1ku0dI77?0v-nqSAn+FHD{k=Q8w)_Q-Lz9n;9p zBAD@LCVz#i*A_s{>9tAo)52meIhyJE_dnv8=gc%j>t?LN{%heIqViOzVR9D`ThK0{ zkLNO>gL*p&PWN1|5B>o;A~7vS<-WPrOYa~PRO~%hdC7-vDTz(U9X%=4@p|y~^aZ%+ z2-NLX4w~&x5Sd%xf&nC6uvpp2u%xNLyL$PSSp>R#Ut{ AH2?qr delta 2210 zcmYjTdpMN)8vT8r8FMi%8B-=@eRqrpuHt?K;ub zl&&YDDB3bJ;zVtGqjroa*Mu-`nbCY^w*EWodH;LY`^Q?(dY@hcK}I-E`M6Y1;U1$U+?sw=eGwn_JmiWbxT zCN{0-T#Ot3-pdEKeWHgCj)r+f6U?!x(wQa~GYwWMkg^2yH{{_<-&o{JAhatus4Ez(X`QpPQF^cwPbw)&SSa?cYv3*)) zL;dpbZR`gd@j;G%?Y(5Lbw0`~^rjaM_Tx57Z6}G&o}CoIPYx6r5`VGDKFXrm_p;uM zEC-#Oe1zH>vijB@$Eirn?S3IA>eD+BUUqxqi=-k~~EU+`tx zfe?_EZcrI@Nz+lGG)4>1$*z{`9QTx6oJ%TG449L%ljcGk?{>e8KfaQRAE5~e1W zwtwd>@=keS;FTqs%SBd0E)*Vo*fjoU_1@BY(BY(q3yaWwML0jYvs4N$gfJNR1L-Dt zog*$lePy+EznY}F%BfHIyXC{joYuQJtz*Z^lglDz`C}<|h?uL}h|(QuN042KR41

FpkYR?*Y3Q;JiWa*Y zi4T2;{xuOBY@WU&5VjIZuGkO}{+os-vp%ZRp~bsAkm{p0l-lzte`$m@0ukJYEr3% z&49b#OX!o9M=f;|rs6(Q#ot%#!Uwb$eOaFoV3CH@B>)(Xf;zs_9*~Oi=zu2ik4A7rEmj8SZRJ>*|I0^$Yn;@83HNqs!Qmz}~8T zwA~B1Rc^G`x0K&!vodMSDLPyCZ1dg~K*=vUv%rm-uqOFe>1SW?5DwV_8y-hW){I{V z3<(b1$c8Gx-R f$rk_j|3M=MRleT_AKX~;WAEd+ee 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();