diff --git a/core/src/main/assets/interfaces/hero_icons.png b/core/src/main/assets/interfaces/hero_icons.png index ec1da3260..462fc72fb 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/interfaces/talent_icons.png b/core/src/main/assets/interfaces/talent_icons.png index 38e831136..52edff8a1 100644 Binary files a/core/src/main/assets/interfaces/talent_icons.png and b/core/src/main/assets/interfaces/talent_icons.png differ diff --git a/core/src/main/assets/messages/actors/actors.properties b/core/src/main/assets/messages/actors/actors.properties index 267185f2e..03447ba1c 100644 --- a/core/src/main/assets/messages/actors/actors.properties +++ b/core/src/main/assets/messages/actors/actors.properties @@ -579,6 +579,7 @@ actors.hero.spells.cleanse.desc=The Cleric instantly clears all harmful effects actors.hero.spells.clericspell.prompt=Choose a target actors.hero.spells.clericspell.no_target=There is no target there. actors.hero.spells.clericspell.invalid_target=You can't target that location. +actors.hero.spells.clericspell.invalid_enemy=You can't target that enemy. actors.hero.spells.clericspell.charge_cost=Charge cost: %d actors.hero.spells.divinesense.name=divine sense @@ -632,7 +633,6 @@ actors.hero.spells.holyward$holyarmbuff.name=holy ward actors.hero.spells.holyward$holyarmbuff.desc=The Cleric has imbued their worn armor with holy energy, temporarily overriding any existing glyph and causing the armor to block an extra 1 point of damage.\n\nTurns Remaining: %s. actors.hero.spells.holyward$holyarmbuff.desc_paladin=The Paladin has imbued their worn armor with holy energy, causing the armor to block an extra 3 points of damage.\n\nCasting other spells while holy ward is active will extend its duration by 10 turns per charge spent.\n\nTurns Remaining: %s. - actors.hero.spells.holyweapon.name=holy weapon actors.hero.spells.holyweapon.ench_name=holy %s actors.hero.spells.holyweapon.ench_desc=Enemies struck by a holy weapon will take extra magical damage. @@ -675,6 +675,10 @@ actors.hero.spells.spiritform.name=spirit form actors.hero.spells.spiritform.short_desc=TODO actors.hero.spells.spiritform.desc=TODO +actors.hero.spells.smite.name=smite +actors.hero.spells.smite.short_desc=Guarantees a hit with bonus damage and enchant power. +actors.hero.spells.smite.desc=The Paladin infuses a melee strike with righteous destructive power.\n\nIn addition to dealing normal melee damage, smite is guaranteed to hit, has +300%% enchantment power, and deals %1$d - %2$d bonus magic damage.\n\nSmite's bonus magic damage scales with the Paladin's level, and it will always deal maximum bonus magic damage against undead or demonic foes. + actors.hero.spells.sunray.name=sunray actors.hero.spells.sunray.short_desc=Deals ranged magic damage and blinds a target once. actors.hero.spells.sunray.desc=The Cleric fires a ray of blinding light at a target, dealing %1$d-%2$d damage and blinding them for %3$d turns. Sunray always deals maximum damage to undead and demonic targets.\n\nAfter being struck with this spell an enemy's vision will adjust, preventing them from being blinded by it again. However, if they are struck again while blinded by this spell, then the light is overwhelming and paralyses them instead. diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/Hero.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/Hero.java index 946b304a4..94929f487 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/Hero.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/Hero.java @@ -74,6 +74,7 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.warrior.En import com.shatteredpixel.shatteredpixeldungeon.actors.hero.spells.HallowedGround; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.spells.HolyWard; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.spells.HolyWeapon; +import com.shatteredpixel.shatteredpixeldungeon.actors.hero.spells.Smite; import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mimic; import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mob; import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Monk; @@ -1454,6 +1455,10 @@ public class Hero extends Char { int dmg = subClass == HeroSubClass.PALADIN ? 6 : 2; enemy.damage(Math.round(dmg * Weapon.Enchantment.genericProcChanceMultiplier(this)), HolyWeapon.INSTANCE); } + + if (buff(Smite.SmiteTracker.class) != null){ + enemy.damage(Smite.bonusDmg(this, enemy), Smite.INSTANCE); + } switch (subClass) { case SNIPER: 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 3dad801e2..359d40935 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 @@ -135,8 +135,7 @@ public abstract class ClericSpell { spells.add(Radiance.INSTANCE); } else if (cleric.subClass == HeroSubClass.PALADIN){ - //TODO innate smite spell - + spells.add(Smite.INSTANCE); } if (cleric.hasTalent(Talent.CLEANSE)){ @@ -146,15 +145,15 @@ public abstract class ClericSpell { if (cleric.hasTalent(Talent.HOLY_LANCE)){ spells.add(HolyLance.INSTANCE); } - if (cleric.hasTalent(Talent.HALLOWED_GROUND)){ spells.add(HallowedGround.INSTANCE); } - if (cleric.hasTalent(Talent.MNEMONIC_PRAYER)){ spells.add(MnemonicPrayer.INSTANCE); } + //TODO paladin spells + } else if (tier == 4){ if (cleric.hasTalent(Talent.DIVINE_INTERVENTION)){ diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/spells/Smite.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/spells/Smite.java new file mode 100644 index 000000000..803499531 --- /dev/null +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/spells/Smite.java @@ -0,0 +1,114 @@ +/* + * Pixel Dungeon + * Copyright (C) 2012-2015 Oleg Dolya + * + * Shattered Pixel Dungeon + * Copyright (C) 2014-2025 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.actors.hero.spells; + +import com.shatteredpixel.shatteredpixeldungeon.Assets; +import com.shatteredpixel.shatteredpixeldungeon.Dungeon; +import com.shatteredpixel.shatteredpixeldungeon.actors.Actor; +import com.shatteredpixel.shatteredpixeldungeon.actors.Char; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.FlavourBuff; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Invisibility; +import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero; +import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.HolyTome; +import com.shatteredpixel.shatteredpixeldungeon.messages.Messages; +import com.shatteredpixel.shatteredpixeldungeon.ui.AttackIndicator; +import com.shatteredpixel.shatteredpixeldungeon.ui.HeroIcon; +import com.shatteredpixel.shatteredpixeldungeon.utils.GLog; +import com.watabou.noosa.audio.Sample; +import com.watabou.utils.Callback; +import com.watabou.utils.Random; + +public class Smite extends TargetedClericSpell { + + public static Smite INSTANCE = new Smite(); + + @Override + public int icon() { + return HeroIcon.SMITE; + } + + @Override + public String desc() { + int min = 5 + Dungeon.hero.lvl/2; + int max = 10 + Dungeon.hero.lvl; + return Messages.get(this, "desc", min, max) + "\n\n" + Messages.get(this, "charge_cost", (int)chargeUse(Dungeon.hero)); + } + + @Override + public float chargeUse(Hero hero) { + return 2f; + } + + @Override + protected void onTargetSelected(HolyTome tome, Hero hero, Integer target) { + if (target == null) { + return; + } + + Char enemy = Actor.findChar(target); + if (enemy == null || enemy == hero){ + GLog.w(Messages.get(this, "no_target")); + return; + } + + //we apply here because of projecting + SmiteTracker tracker = Buff.affect(hero, SmiteTracker.class); + if (hero.isCharmedBy(enemy) || !Dungeon.level.heroFOV[target] || !hero.canAttack(enemy)) { + GLog.w(Messages.get(this, "invalid_enemy")); + tracker.detach(); + return; + } + + hero.sprite.attack(enemy.pos, new Callback() { + @Override + public void call() { + AttackIndicator.target(enemy); + + if (hero.attack(enemy, 1, 0, Char.INFINITE_ACCURACY)){ + Sample.INSTANCE.play(Assets.Sounds.HIT_STRONG); + enemy.sprite.burst(0xFFFFFFFF, 10); + } + tracker.detach(); + + Invisibility.dispel(); + + hero.spendAndNext(hero.attackDelay()); + onSpellCast(tome, hero); + } + }); + + } + + public static int bonusDmg( Hero attacker, Char defender){ + int min = 5 + attacker.lvl/2; + int max = 10 + attacker.lvl; + if (Char.hasProp(defender, Char.Property.UNDEAD) || Char.hasProp(defender, Char.Property.DEMONIC)){ + return max; + } else { + return Random.NormalIntRange(min, max); + } + } + + public static class SmiteTracker extends FlavourBuff {}; + +} diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/armor/glyphs/AntiMagic.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/armor/glyphs/AntiMagic.java index c64e81cfc..26d47d321 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/armor/glyphs/AntiMagic.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/armor/glyphs/AntiMagic.java @@ -35,6 +35,7 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.mage.WarpB import com.shatteredpixel.shatteredpixeldungeon.actors.hero.spells.GuidingLight; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.spells.HolyLance; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.spells.HolyWeapon; +import com.shatteredpixel.shatteredpixeldungeon.actors.hero.spells.Smite; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.spells.Sunray; import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.CrystalWisp; import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.DM100; @@ -97,6 +98,7 @@ public class AntiMagic extends Armor.Glyph { RESISTS.add( HolyWeapon.class ); RESISTS.add( Sunray.class ); RESISTS.add( HolyLance.class ); + RESISTS.add( Smite.class ); RESISTS.add( Judgement.class ); RESISTS.add( ElementalBlast.class ); diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/weapon/Weapon.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/weapon/Weapon.java index 8ebe0c860..60c6e9298 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/weapon/Weapon.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/weapon/Weapon.java @@ -32,6 +32,7 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Talent; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.cleric.AscendedForm; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.duelist.ElementalStrike; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.spells.HolyWeapon; +import com.shatteredpixel.shatteredpixeldungeon.actors.hero.spells.Smite; import com.shatteredpixel.shatteredpixeldungeon.items.Item; import com.shatteredpixel.shatteredpixeldungeon.items.KindOfWeapon; import com.shatteredpixel.shatteredpixeldungeon.items.bags.Bag; @@ -130,6 +131,10 @@ abstract public class Weapon extends KindOfWeapon { } else if (enchantment != null) { damage = enchantment.proc(this, attacker, defender, damage); } + if (attacker instanceof Hero && isEquipped((Hero) attacker) && + attacker.buff(Smite.SmiteTracker.class) != null){ + defender.damage(Smite.bonusDmg((Hero) attacker, defender), Smite.INSTANCE); + } } if (!levelKnown && attacker == Dungeon.hero) { @@ -488,6 +493,10 @@ abstract public class Weapon extends KindOfWeapon { attacker.buff(RunicBlade.RunicSlashTracker.class).detach(); } + if (attacker.buff(Smite.SmiteTracker.class) != null){ + multi += 3f; + } + if (attacker.buff(ElementalStrike.DirectedPowerTracker.class) != null){ multi += attacker.buff(ElementalStrike.DirectedPowerTracker.class).enchBoost; attacker.buff(ElementalStrike.DirectedPowerTracker.class).detach(); 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 445ecb810..cd70ba729 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/ui/HeroIcon.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/ui/HeroIcon.java @@ -35,7 +35,7 @@ public class HeroIcon extends Image { private static final int SIZE = 16; //transparent icon - public static final int NONE = 63; + public static final int NONE = 127; //subclasses public static final int BERSERKER = 0; @@ -87,7 +87,10 @@ public class HeroIcon extends Image { public static final int HOLY_LANCE = 51; public static final int HALLOWED_GROUND = 52; public static final int MNEMONIC_PRAYER = 53; - //Paladin spells go here + public static final int SMITE = 54; + public static final int LAY_ON_HANDS = 55; + //56 + //57 public static final int DIVINE_INTERVENTION = 58; public static final int JUDGEMENT = 59; public static final int FLASH = 60;