v3.0.0: added 'quick cast' functionality for cleric spells

This commit is contained in:
Evan Debenham
2024-11-27 14:15:40 -05:00
parent 3966232082
commit ee2f373e61
8 changed files with 201 additions and 20 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -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.

View File

@@ -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)

View File

@@ -119,6 +119,19 @@ public abstract class ClericSpell {
}
return spells;
};
}
public static ArrayList<ClericSpell> getAllSpells() {
ArrayList<ClericSpell> 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;
}
}

View File

@@ -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);
}
}
});

View File

@@ -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));
}
}
}
}
}

View File

@@ -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 );

View File

@@ -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();