v0.9.3: implemented the redesigned spectral blades ability

This commit is contained in:
Evan Debenham 2021-05-08 23:06:58 -04:00
parent b20cc11dee
commit 3d0bb462c2
4 changed files with 120 additions and 42 deletions

View File

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

View File

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

View File

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

View File

@ -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<Char> 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<Callback, Mob> targets = new HashMap<>();
final HashSet<Callback> 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<Char> 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};
}
}