diff --git a/core/src/main/assets/messages/actors/actors.properties b/core/src/main/assets/messages/actors/actors.properties index 8ceac1123..ea7c5b075 100644 --- a/core/src/main/assets/messages/actors/actors.properties +++ b/core/src/main/assets/messages/actors/actors.properties @@ -334,13 +334,14 @@ actors.buffs.wellfed.desc=You feel quite satisfied and full.\n\nWhile well fed, ##abilities actors.hero.abilities.armorability.self_target=You can't target yourself! -actors.hero.abilities.armorability.prompt=Choose a target +actors.hero.abilities.armorability.no_target=There's nothing to target there! +actors.hero.abilities.armorability.prompt=Choose a location to target actors.hero.abilities.warrior.heroicleap.name=heroic leap actors.hero.abilities.warrior.heroicleap.desc=The Warrior performs a heroic leap towards a targeted location, slamming down to stun all neighbouring enemies. Consumes 35 charge. actors.hero.abilities.warrior.heroicleap.prompt=Choose direction to leap actors.hero.abilities.warrior.shockwave.name=shockwave -actors.hero.abilities.warrior.shockwave.desc=The Warrior slams the ground and releases a shockwave in a conical AOE. Enemies caught in the shockwave are damaged and crippled. Comsumes 35 energy. +actors.hero.abilities.warrior.shockwave.desc=The Warrior slams the ground and releases a shockwave in a conical AOE. Enemies caught in the shockwave are damaged and crippled. Consumes 35 energy. actors.hero.abilities.warrior.warrior3.name=??? actors.hero.abilities.warrior.warrior3.desc=I haven't decided on this ability yet. actors.hero.abilities.mage.moltenearth.name=molten earth @@ -351,14 +352,14 @@ actors.hero.abilities.mage.mage3.name=??? actors.hero.abilities.mage.mage3.desc=I haven't decided on this ability yet. actors.hero.abilities.rogue.smokebomb.name=smoke bomb actors.hero.abilities.rogue.smokebomb.fov=You can only jump to an empty location in your field of view -actors.hero.abilities.rogue.smokebomb.desc=The Rogue blinks to any nearby location which he can see, leaving a plume of smoke where he stood. This ability makes the rogue temporarily invisible, and blinds any enemies adjacent to his old location. +actors.hero.abilities.rogue.smokebomb.desc=The Rogue blinks to any nearby location which he can see, leaving a plume of smoke where he stood. This ability makes the rogue temporarily invisible, and blinds any enemies adjacent to his old location. Consumes 35 charge. actors.hero.abilities.rogue.smokebomb.prompt=Choose a location to jump to actors.hero.abilities.rogue.rogue2.name=??? actors.hero.abilities.rogue.rogue2.desc=I haven't decided on this ability yet. actors.hero.abilities.rogue.rogue3.name=??? actors.hero.abilities.rogue.rogue3.desc=I haven't decided on this ability yet. actors.hero.abilities.huntress.spectralblades.name=spectral blades -actors.hero.abilities.huntress.spectralblades.desc=The Huntress creates a fan of spectral blades. Each of these blades will target a single enemy in the huntress's field of view, inflicting damage depending on her currently equipped melee weapon. +actors.hero.abilities.huntress.spectralblades.desc=The Huntress throws a spectral blade at a target, inflicting damage depending on her currently equipped melee weapon. Consumes 35 charge. actors.hero.abilities.huntress.huntress2.name=??? actors.hero.abilities.huntress.huntress2.desc=I haven't decided on this ability yet. actors.hero.abilities.huntress.huntress3.name=??? @@ -647,12 +648,12 @@ actors.hero.talent.barkskin.desc=_+1:_ When stepping in grass, the Warden gains actors.hero.talent.shielding_dew.title=shielding dew actors.hero.talent.shielding_dew.desc=_+1:_ Dewdrops can shield the Warden when her health is full, up to _20% of her max HP_.\n\n_+2:_ Dewdrops can shield the Warden when her health is full, up to _40% of her max HP_.\n\n_+3:_ Dewdrops can shield the Warden when her health is full, up to _60% of her max HP_.actors.hero.talent.hearty_meal.title=hearty meal -actors.hero.talent.spectral_blades_1.title=TODO NAME -actors.hero.talent.spectral_blades_1.desc=TODO DESC -actors.hero.talent.spectral_blades_2.title=TODO NAME -actors.hero.talent.spectral_blades_2.desc=TODO DESC -actors.hero.talent.spectral_blades_3.title=TODO NAME -actors.hero.talent.spectral_blades_3.desc=TODO DESC +actors.hero.talent.fan_of_blades.title=fan of blades +actors.hero.talent.fan_of_blades.desc=_+1:_ Spectral blades can hit up to _1 additional target_ for 50% damage. The target must be visible and within a _30 degree_ cone AOE.\n\n_+1:_ Spectral blades can hit up to _2 additional targets_ for 50% damage. The targets must be visible and within a _60 degree_ cone AOE.\n\n_+1:_ Spectral blades can hit up to _3 additional targets_ for 50% damage. The targets must be visible and within a _90 degree_ cone AOE.\n\n_+1:_ Spectral blades can hit up to _4 additional targets_ for 50% damage. The targets must be visible and within a _120 degree_ cone AOE. +actors.hero.talent.projecting_blades.title=projecting blades +actors.hero.talent.projecting_blades.desc=_+1:_ Spectral blades has _+25% accuracy_, and can penetrate up to _2 solid tiles_.\n\n_+2:_ Spectral blades has _+50% accuracy_, and can penetrate up to _4 solid tiles_.\n\n_+3:_ Spectral blades has _+75% accuracy_, and can penetrate up to _6 solid tiles_.\n\n_+4:_ Spectral blades has _+100% accuracy_, and can penetrate up to _8 solid tiles_. +actors.hero.talent.spirit_blades.title=spirit blades +actors.hero.talent.spirit_blades.desc=_+1:_ Spectral blades has a _25% chance_ to also use the enchantment on the spirit bow.\n\n_+2:_ Spectral blades has a _50% chance_ to also use the enchantment on the spirit bow.\n\n_+3:_ Spectral blades has a _75% chance_ to also use the enchantment on the spirit bow.\n\n_+4:_ Spectral blades has a _100% chance_ to also use the enchantment on the spirit bow. actors.hero.talent.huntress_2_1.title=TODO NAME actors.hero.talent.huntress_2_1.desc=TODO DESC 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 13aec2598..e5e54e955 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 @@ -1099,6 +1099,13 @@ public class Hero extends Char { if (wep != null) damage = wep.proc( this, enemy, damage ); + if (buff(Talent.SpiritBladesTracker.class) != null + && Random.Int(4) < pointsInTalent(Talent.SPIRIT_BLADES)){ + SpiritBow bow = belongings.getItem(SpiritBow.class); + if (bow != null) damage = bow.proc( this, enemy, damage ); + buff(Talent.SpiritBladesTracker.class).detach(); + } + damage = Talent.onAttackProc( this, enemy, damage ); switch (subClass) { diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/Talent.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/Talent.java index e98055714..928d7cbb9 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/Talent.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/Talent.java @@ -130,7 +130,7 @@ public enum Talent { //Warden T3 DURABLE_TIPS(110, 3), BARKSKIN(111, 3), SHIELDING_DEW(112, 3), //Spectral Blades T4 - SPECTRAL_BLADES_1(113, 4), SPECTRAL_BLADES_2(114, 4), SPECTRAL_BLADES_3(115, 4), + FAN_OF_BLADES(113, 4), PROJECTING_BLADES(114, 4), SPIRIT_BLADES(115, 4), //??? T4 HUNTRESS_2_1(116, 4), HUNTRESS_2_2(117, 4), HUNTRESS_2_3(118, 4), //??? T4 @@ -149,6 +149,7 @@ public enum Talent { public static class RejuvenatingStepsCooldown extends FlavourBuff{}; public static class RejuvenatingStepsFurrow extends CounterBuff{}; public static class SeerShotCooldown extends FlavourBuff{}; + public static class SpiritBladesTracker extends FlavourBuff{}; int icon; int maxPoints; diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/abilities/huntress/SpectralBlades.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/abilities/huntress/SpectralBlades.java index 079a1565b..a8665b87d 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/abilities/huntress/SpectralBlades.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/abilities/huntress/SpectralBlades.java @@ -22,68 +22,137 @@ package com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.huntress; 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.actors.hero.Talent; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.ArmorAbility; -import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mob; import com.shatteredpixel.shatteredpixeldungeon.items.Item; import com.shatteredpixel.shatteredpixeldungeon.items.armor.ClassArmor; import com.shatteredpixel.shatteredpixeldungeon.items.weapon.missiles.Shuriken; +import com.shatteredpixel.shatteredpixeldungeon.mechanics.Ballistica; +import com.shatteredpixel.shatteredpixeldungeon.mechanics.ConeAOE; import com.shatteredpixel.shatteredpixeldungeon.messages.Messages; import com.shatteredpixel.shatteredpixeldungeon.sprites.MissileSprite; import com.shatteredpixel.shatteredpixeldungeon.utils.GLog; import com.watabou.utils.Callback; -import java.util.HashMap; +import java.util.HashSet; public class SpectralBlades extends ArmorAbility { + @Override + protected String targetingPrompt() { + return Messages.get(this, "prompt"); + } + @Override protected void activate(ClassArmor armor, Hero hero, Integer target) { - armor.charge -= 35; + if (target == null){ + return; + } + + if (Actor.findChar(target) == hero){ + GLog.w(Messages.get(this, "self_target")); + return; + } + + Ballistica b = new Ballistica(hero.pos, target, Ballistica.WONT_STOP); + final HashSet targets = new HashSet<>(); + + Char enemy = findChar(b, hero, 2*hero.pointsInTalent(Talent.PROJECTING_BLADES), targets); + + if (enemy == null){ + GLog.w(Messages.get(this, "no_target")); + return; + } + + targets.add(enemy); + + if (hero.hasTalent(Talent.FAN_OF_BLADES)){ + ConeAOE cone = new ConeAOE(b, 30*hero.pointsInTalent(Talent.FAN_OF_BLADES)); + for (Ballistica ray : cone.rays){ + Char toAdd = findChar(ray, hero, 2*hero.pointsInTalent(Talent.PROJECTING_BLADES), targets); + if (toAdd != null && hero.fieldOfView[toAdd.pos]){ + targets.add(toAdd); + } + } + while (targets.size() > 1 + hero.pointsInTalent(Talent.FAN_OF_BLADES)){ + Char furthest = null; + for (Char ch : targets){ + if (furthest == null){ + furthest = ch; + } else if (Dungeon.level.trueDistance(enemy.pos, ch.pos) > + Dungeon.level.trueDistance(enemy.pos, furthest.pos)){ + furthest = ch; + } + } + targets.remove(furthest); + } + } + + armor.charge -= chargeUse(hero); Item.updateQuickslot(); Item proto = new Shuriken(); - final HashMap targets = new HashMap<>(); + final HashSet callbacks = new HashSet<>(); - for (Mob mob : Dungeon.level.mobs) { - if (Dungeon.level.distance(hero.pos, mob.pos) <= 12 - && Dungeon.level.heroFOV[mob.pos] - && mob.alignment != Char.Alignment.ALLY) { - - Callback callback = new Callback() { - @Override - public void call() { - hero.attack( targets.get( this ) ); - targets.remove( this ); - if (targets.isEmpty()) { - Invisibility.dispel(); - hero.spendAndNext( hero.attackDelay() ); - } + for (Char ch : targets) { + Callback callback = new Callback() { + @Override + public void call() { + float dmgMulti = ch == enemy ? 1f : 0.5f; + float accmulti = 1f + 0.25f*hero.pointsInTalent(Talent.PROJECTING_BLADES); + if (hero.hasTalent(Talent.SPIRIT_BLADES)){ + Buff.affect(hero, Talent.SpiritBladesTracker.class, 0f); } - }; + hero.attack( ch, dmgMulti, 0, accmulti ); + callbacks.remove( this ); + if (callbacks.isEmpty()) { + Invisibility.dispel(); + hero.spendAndNext( hero.attackDelay() ); + } + } + }; - ((MissileSprite)hero.sprite.parent.recycle( MissileSprite.class )). - reset( hero.sprite, mob.pos, proto, callback ); + ((MissileSprite)hero.sprite.parent.recycle( MissileSprite.class )). + reset( hero.sprite, ch.pos, proto, callback ); - targets.put( callback, mob ); + callbacks.add( callback ); + } + + hero.sprite.zap( enemy.pos ); + hero.busy(); + } + + private Char findChar(Ballistica path, Hero hero, int wallPenetration, HashSet existingTargets){ + for (int cell : path.path){ + Char ch = Actor.findChar(cell); + if (ch != null){ + if (ch == hero || existingTargets.contains(ch)){ + continue; + } else if (ch.alignment != Char.Alignment.ALLY){ + return ch; + } else { + return null; + } + } + if (Dungeon.level.solid[cell]){ + wallPenetration--; + if (wallPenetration < 0){ + return null; + } } } - - if (targets.size() == 0) { - GLog.w( Messages.get(this, "no_enemies") ); - return; - } - - hero.sprite.zap( hero.pos ); - hero.busy(); + return null; } @Override public Talent[] talents() { - return new Talent[]{Talent.SPECTRAL_BLADES_1, Talent.SPECTRAL_BLADES_2, Talent.SPECTRAL_BLADES_3, Talent.HEROIC_ENERGY}; + return new Talent[]{Talent.FAN_OF_BLADES, Talent.PROJECTING_BLADES, Talent.SPIRIT_BLADES, Talent.HEROIC_ENERGY}; } }