diff --git a/core/src/main/assets/interfaces/hero_icons.png b/core/src/main/assets/interfaces/hero_icons.png
index d33a0a7ce..7eedc2f63 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 eac942be0..f7153c65b 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 557e3c11d..320af8d62 100644
--- a/core/src/main/assets/messages/actors/actors.properties
+++ b/core/src/main/assets/messages/actors/actors.properties
@@ -467,6 +467,10 @@ actors.hero.abilities.duelist.challenge.short_desc=The Duelist _challenges_ a ne
actors.hero.abilities.duelist.challenge.desc=The Duelist challenges a nearby enemy to a duel. That enemy is compelled to fight her while all other enemies are temporarily frozen in time.\n\nThe target must be reachable and within 5 tiles of the Duelist. The duel lasts until 10 turns pass, the enemy dies, or the Duelist is more than 5 tiles away from the enemy.\n\nFrozen enemies are invulnerable. The Duelist's allies are not frozen by this ability, but if a boss is targeted its minions will not be frozen either.
actors.hero.abilities.duelist.challenge$duelparticipant.name=duel participant
actors.hero.abilities.duelist.challenge$duelparticipant.desc=This character is participating in a duel. They, and any of their allies or minions, are allowed to act normally while others are frozen.\n\nThe duel will last for a set amount of time, until one participant dies, or until the participants move more than 5 tiles away from eachother.\n\nTurns remaining: %d.
+actors.hero.abilities.duelist.elementalstrike.name=elemental strike
+actors.hero.abilities.duelist.elementalstrike.short_desc=The Duelist performs an _elemental strike_, spreading an effect in a conical AOE based on her weapon enchantment.
+actors.hero.abilities.duelist.elementalstrike.desc=The Duelist strikes an enemy or location, performing a regular attack that's guaranteed to hit and spreading a magical effect that travels up to 3 tiles in a 65 degree cone. This magical effect varies based on the enchantment on the Duelist's primary weapon.
+actors.hero.abilities.duelist.elementalstrike.generic_desc=An elemental strike with no enchantment will release a small burst of magic, dealing 5-10 damage to all enemies in range.
actors.hero.abilities.ratmogrify.name=ratmogrify
actors.hero.abilities.ratmogrify.cant_transform=You can't ratmogrify that!
@@ -870,7 +874,12 @@ actors.hero.talent.invigorating_victory.desc=_+1:_ If the Duelist defeats her ta
actors.hero.talent.elimination_match.title=elimination match
actors.hero.talent.elimination_match.desc=_+1:_ If the Duelist challenges again within 3 turns of a duel ending, that challenge has a _20% reduced_ charge cost.\n\n_+2:_ If the Duelist challenges again within 3 turns of a duel ending, that challenge has a _36% reduced_ charge cost.\n\n_+3:_ If the Duelist challenges again within 3 turns of a duel ending, that challenge has a _50% reduced_ charge cost.\n\n_+4:_ If the Duelist challenges again within 3 turns of a duel ending, that challenge has a _60% reduced_ charge cost.
-#second armor ability
+actors.hero.talent.elemental_reach.title=elemental reach
+actors.hero.talent.elemental_reach.desc=_+1:_ Elemental strike's range is increased to _4 tiles_ from 3, and its width is increased to _70 degrees_ from 65.\n\n_+2:_ Elemental strike's range is increased to _5 tiles_ from 3, and its width is increased to _75 degrees_ from 65.\n\n_+3:_ Elemental strike's range is increased to _6 tiles_ from 3, and its width is increased to _80 degrees_ from 65.\n\n_+4:_ Elemental strike's range is increased to _7 tiles_ from 3, and its width is increased to _85 degrees_ from 65.
+actors.hero.talent.striking_force.title=striking force
+actors.hero.talent.striking_force.desc=_+1:_ The power of elemental strike's effect is increased by _25%_.\n\n_+2:_ The power of elemental strike's effect is increased by _50%_.\n\n_+3:_ The power of elemental strike's effect is increased by _75%_.\n\n_+4:_ The power of elemental strike's effect is increased by _100%_.
+actors.hero.talent.directed_power.title=directed power
+actors.hero.talent.directed_power.desc=_+1:_ The direct attack from elemental strike gains _+25% enchantment power_ for each enemy in range of elemental strike's effect, including the attack target.\n\n_+2:_ The direct attack from elemental strike gains _+50% enchantment power_ for each enemy in range of elemental strike's effect, including the attack target.\n\n_+3:_ The direct attack from elemental strike gains _+75% enchantment power_ for each enemy in range of elemental strike's effect, including the attack target.\n\n_+4:_ The direct attack from elemental strike gains _+100% enchantment power_ for each enemy in range of elemental strike's effect, including the attack target.
#third armor ability
diff --git a/core/src/main/assets/messages/items/items.properties b/core/src/main/assets/messages/items/items.properties
index 59581da68..4849ff648 100644
--- a/core/src/main/assets/messages/items/items.properties
+++ b/core/src/main/assets/messages/items/items.properties
@@ -1406,12 +1406,15 @@ items.weapon.curses.annoying.msg_11=I didn't want to be a weapon, I wanted to be
items.weapon.curses.annoying.msg_12=Remember, overconfidence is a slow and insidious killer.
items.weapon.curses.annoying.msg_13=ALL YOUR BASE ARE BELONG TO US!
items.weapon.curses.annoying.desc=Annoying weapons are capable of speech, but they're a bit too energetic. They will often draw attention to you without meaning to.
+items.weapon.curses.annoying.elestrike_desc=An elemental strike with an annoying curse has a 20% chance to amok each enemy in range for 5 turns.
items.weapon.curses.dazzling.name=dazzling %s
items.weapon.curses.dazzling.desc=Dazzling weapons will sometimes flash with dazzling light, blinding everything that can see them.
+items.weapon.curses.dazzling.elestrike_desc=An elemental strike with a dazzling curse has a 50% chance to blind each enemy in range for 5 turns.
items.weapon.curses.displacing.name=displacing %s
items.weapon.curses.displacing.desc=Displacing weapons are infused with chaotic teleportation magic, possessing the ability to warp enemies around the floor randomly.
+items.weapon.curses.displacing.elestrike_desc=An elemental strike with a displacing curse has a 50% chance to teleport each enemy in range.
items.weapon.curses.explosive.name=explosive %s
items.weapon.curses.explosive.warm=Warm...
@@ -1420,67 +1423,82 @@ items.weapon.curses.explosive.desc=Explosive weapons steadily build up power and
items.weapon.curses.explosive.desc_cool=Your weapon is currently cool to the touch.
items.weapon.curses.explosive.desc_warm=Your weapon is building energy and getting warm...
items.weapon.curses.explosive.desc_hot=Your weapon is hot! It's about to explode!
+items.weapon.curses.explosive.elestrike_desc=An elemental strike with an explosive curse has a 50% chance to cause an explosion to appear on a random enemy in range.
items.weapon.curses.friendly.name=friendly %s
items.weapon.curses.friendly.desc=Friendly weapons are best suited for pacifists, occasionally triggering magic that makes it impossible to fight.
+items.weapon.curses.friendly.elestrike_desc=An elemental strike with a friendly curse has a 50% chance to charm each enemy in range for 5 turns.
items.weapon.curses.polarized.name=polarized %s
items.weapon.curses.polarized.desc=A polarized weapon is affected by magic that causes its attack to either deal 50% more damage, or no damage at all.
+items.weapon.curses.polarized.elestrike_desc=An elemental strike with a polarized curse has a 50% chance to deal 20-30 damage to each enemy in range.
items.weapon.curses.sacrificial.name=sacrificial %s
items.weapon.curses.sacrificial.desc=Sacrificial weapons will demand blood from the wearer in return for attacking foes. The more healthy the wearer is, the more blood the curse will take.
+items.weapon.curses.sacrificial.elestrike_desc=An elemental strike with a sacrificial curse causes the hero and each enemy in range to bleed for 10 HP.
items.weapon.curses.wayward.name=wayward %s
items.weapon.curses.wayward.desc=Wayward weapons will sometimes become extremely inaccurate. This magic lasts for a little while when it activates, but can be dispelled by landing a blow with the wayward weapon.
+items.weapon.curses.wayward.elestrike_desc=An elemental strike with a wayward curse has a 50% chance to hex each enemy in range for 5 turns.
items.weapon.curses.wayward$waywardbuff.name=wayward
items.weapon.curses.wayward$waywardbuff.desc=Your wayward weapon's magic has activated, making it extremely inaccurate for a short time. Note that this does not affect attacks which are guaranteed to hit, such as surprise attacks. Successfully attacking with the wayward weapon will clear this effect immediately.\n\nTurns remaining: %s.
###enchantments
items.weapon.enchantments.blazing.name=blazing %s
items.weapon.enchantments.blazing.desc=This enchantment causes flames to spit forth from a weapon, igniting enemies and dealing bonus damage to enemies that are already aflame.
+items.weapon.enchantments.blazing.elestrike_desc=An elemental strike with a blazing enchantment spreads fire to every tile in range, which lasts 6 turns.
items.weapon.enchantments.blocking.name=blocking %s
items.weapon.enchantments.blocking.desc=Blocking weapons have a chance to briefly shield you after attacking with them.
+items.weapon.enchantments.blocking.elestrike_desc=An elemental strike with a blocking enchantment grants the Duelist an extra 5 shielding for each enemy in range.
items.weapon.enchantments.blocking$blockbuff.name=blocking
items.weapon.enchantments.blocking$blockbuff.desc=Your weapon's blocking enchantment has granted you a brief boost to your defensive power!\n\nShielding remaining: %d\n\nTurns remaining: %s.
items.weapon.enchantments.blooming.name=blooming %s
items.weapon.enchantments.blooming.desc=Blooming weapons contain magic which will cause vegetation to sprout on or around those struck by them.
+items.weapon.enchantments.blooming.elestrike_desc=An elemental strike with a blooming enchantment spreads grass to up to 6 tiles in range, and roots all enemies in range for 5 turns.
items.weapon.enchantments.chilling.name=chilling %s
items.weapon.enchantments.chilling.desc=Enemies struck with this enchantment are chilled, slowing their movement and attacks.
+items.weapon.enchantments.chilling.elestrike_desc=An elemental strike with a chilling enchantment spreads chilling air to every tile in range, which lasts 6 turns.
items.weapon.enchantments.kinetic.name=kinetic %s
items.weapon.enchantments.kinetic.desc=When an enemy is killed with a kinetic weapon, any excess force is stored in the weapon and will be applied to the next successful attack.
+items.weapon.enchantments.kinetic.elestrike_desc=An elemental strike with a kinetic enchantment applies 33% of stored damage to each enemy in range, except the primary target.
items.weapon.enchantments.kinetic$conserveddamage.name=conserved damage
items.weapon.enchantments.kinetic$conserveddamage.desc=Your weapon has stored the excess force from a previous killing blow, and will apply it as bonus damage to your next attack. The energy will slowly fade over time, however.\n\nConserved Damage: %d.
items.weapon.enchantments.corrupting.name=corrupting %s
items.weapon.enchantments.corrupting.desc=This powerful enchantment possesses the ability to turn enemies to your will. When an enemy is killed with this weapon, there is a chance they will become corrupted instead of dying.
+items.weapon.enchantments.corrupting.elestrike_desc=An elemental strike with a corrupting enchantment has a 4-20% chance (based on missing HP) to corrupt each enemy in range, except the primary target.
items.weapon.enchantments.elastic.name=elastic %s
items.weapon.enchantments.elastic.desc=Elastic weapons have a chance to send enemies flying back short distances.
+items.weapon.enchantments.elastic.elestrike_desc=An elemental strike with an elastic enchantment knocks all enemies in range back 4 tiles.
items.weapon.enchantments.grim.name=grim %s
items.weapon.enchantments.grim.desc=This powerful enchantment possesses the power to instantly execute an enemy. The effect is more likely to occur the weaker the enemy is.
+items.weapon.enchantments.grim.elestrike_desc=An elemental strike with a grim enchantment has up to a 6-30% chance (based on missing HP) to kill each enemy in range, except the primary target.
items.weapon.enchantments.lucky.name=lucky %s
items.weapon.enchantments.lucky.desc=Enemies which are killed by a lucky weapon have a chance to drop extra loot.
-
-items.weapon.enchantments.precise.name=precise %s
-items.weapon.enchantments.precise.desc=A precise weapon has a chance to guarantee a hit on an enemy, regardless of the circumstances.
+items.weapon.enchantments.lucky.elestrike_desc=An elemental strike with a lucky enchantment has a 10% chance to spawn loot under each enemy in range. This effect can only trigger once per enemy.
items.weapon.enchantments.projecting.name=projecting %s
items.weapon.enchantments.projecting.desc=With this enchantment melee weapons will gain extra reach. Ranged weapons will be able to penetrate nearby walls.
+items.weapon.enchantments.projecting.elestrike_desc=An elemental strike with a projecting enchantment deals 25% damage to each enemy in range, except the primary target.
items.weapon.enchantments.shocking.name=shocking %s
items.weapon.enchantments.shocking.desc=Electricity arcs from a shocking weapon, dealing extra damage to all nearby enemies.
+items.weapon.enchantments.shocking.elestrike_desc=An elemental strike with a shocking enchantment spreads electricity to every tile in range, which lasts 6 turns.
items.weapon.enchantments.unstable.name=unstable %s
items.weapon.enchantments.unstable.desc=This enchantment radiates chaotic energy, acting as a different enchantment with each hit.
+items.weapon.enchantments.unstable.elestrike_desc=An elemental strike with an unstable enchantment applies a random enchantment effect to each enemy in range, except the primary target.
items.weapon.enchantments.vampiric.name=vampiric %s
items.weapon.enchantments.vampiric.desc=This powerful enchantment leeches life force from enemies with each blow, funneling it back into the wearer. The effect is stronger the lower the wearer's health.
+items.weapon.enchantments.vampiric.elestrike_desc=An elemental strike with a vampiric enchantment heals the Duelist for 2 HP for each enemy in range.
###melee weapons
diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/HeroClass.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/HeroClass.java
index 9e1c32f7e..2d137efff 100644
--- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/HeroClass.java
+++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/HeroClass.java
@@ -29,6 +29,7 @@ import com.shatteredpixel.shatteredpixeldungeon.QuickSlot;
import com.shatteredpixel.shatteredpixeldungeon.SPDSettings;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.ArmorAbility;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.duelist.Challenge;
+import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.duelist.ElementalStrike;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.huntress.NaturesPower;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.huntress.SpiritHawk;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.huntress.SpectralBlades;
@@ -253,7 +254,7 @@ public enum HeroClass {
case HUNTRESS:
return new ArmorAbility[]{new SpectralBlades(), new NaturesPower(), new SpiritHawk()};
case DUELIST:
- return new ArmorAbility[]{new Challenge()};
+ return new ArmorAbility[]{new Challenge(), new ElementalStrike()};
}
}
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 d0477fd45..edbd9801a 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
@@ -160,7 +160,7 @@ public enum Talent {
//Duelist A1 T4
CLOSE_THE_GAP(145, 4), INVIGORATING_VICTORY(146, 4), ELIMINATION_MATCH(147, 4),
//Duelist A2 T4
- DUELIST_A2_1(148, 4), DUELIST_A2_2(149, 4), DUELIST_A2_3(150, 4),
+ ELEMENTAL_REACH(148, 4), STRIKING_FORCE(149, 4), DIRECTED_POWER(150, 4),
//Duelist A3 T4
DUELIST_A3_1(151, 4), DUELIST_A3_2(152, 4), DUELIST_A3_3(153, 4),
diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/abilities/duelist/ElementalStrike.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/abilities/duelist/ElementalStrike.java
new file mode 100644
index 000000000..e3c8a74de
--- /dev/null
+++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/abilities/duelist/ElementalStrike.java
@@ -0,0 +1,532 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2015 Oleg Dolya
+ *
+ * Shattered Pixel Dungeon
+ * Copyright (C) 2014-2023 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.abilities.duelist;
+
+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.blobs.Blob;
+import com.shatteredpixel.shatteredpixeldungeon.actors.blobs.Electricity;
+import com.shatteredpixel.shatteredpixeldungeon.actors.blobs.Fire;
+import com.shatteredpixel.shatteredpixeldungeon.actors.blobs.Freezing;
+import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.AllyBuff;
+import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Amok;
+import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Barrier;
+import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Bleeding;
+import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Blindness;
+import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff;
+import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Charm;
+import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Corruption;
+import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.FlavourBuff;
+import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Hex;
+import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Invisibility;
+import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Roots;
+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.hero.abilities.mage.ElementalBlast;
+import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mob;
+import com.shatteredpixel.shatteredpixeldungeon.effects.CellEmitter;
+import com.shatteredpixel.shatteredpixeldungeon.effects.MagicMissile;
+import com.shatteredpixel.shatteredpixeldungeon.effects.Speck;
+import com.shatteredpixel.shatteredpixeldungeon.effects.particles.ShadowParticle;
+import com.shatteredpixel.shatteredpixeldungeon.items.Item;
+import com.shatteredpixel.shatteredpixeldungeon.items.KindOfWeapon;
+import com.shatteredpixel.shatteredpixeldungeon.items.armor.ClassArmor;
+import com.shatteredpixel.shatteredpixeldungeon.items.bombs.Bomb;
+import com.shatteredpixel.shatteredpixeldungeon.items.scrolls.ScrollOfTeleportation;
+import com.shatteredpixel.shatteredpixeldungeon.items.wands.WandOfBlastWave;
+import com.shatteredpixel.shatteredpixeldungeon.items.wands.WandOfCorrosion;
+import com.shatteredpixel.shatteredpixeldungeon.items.wands.WandOfCorruption;
+import com.shatteredpixel.shatteredpixeldungeon.items.weapon.Weapon;
+import com.shatteredpixel.shatteredpixeldungeon.items.weapon.curses.Annoying;
+import com.shatteredpixel.shatteredpixeldungeon.items.weapon.curses.Dazzling;
+import com.shatteredpixel.shatteredpixeldungeon.items.weapon.curses.Displacing;
+import com.shatteredpixel.shatteredpixeldungeon.items.weapon.curses.Explosive;
+import com.shatteredpixel.shatteredpixeldungeon.items.weapon.curses.Friendly;
+import com.shatteredpixel.shatteredpixeldungeon.items.weapon.curses.Polarized;
+import com.shatteredpixel.shatteredpixeldungeon.items.weapon.curses.Sacrificial;
+import com.shatteredpixel.shatteredpixeldungeon.items.weapon.curses.Wayward;
+import com.shatteredpixel.shatteredpixeldungeon.items.weapon.enchantments.Blazing;
+import com.shatteredpixel.shatteredpixeldungeon.items.weapon.enchantments.Blocking;
+import com.shatteredpixel.shatteredpixeldungeon.items.weapon.enchantments.Blooming;
+import com.shatteredpixel.shatteredpixeldungeon.items.weapon.enchantments.Chilling;
+import com.shatteredpixel.shatteredpixeldungeon.items.weapon.enchantments.Corrupting;
+import com.shatteredpixel.shatteredpixeldungeon.items.weapon.enchantments.Elastic;
+import com.shatteredpixel.shatteredpixeldungeon.items.weapon.enchantments.Grim;
+import com.shatteredpixel.shatteredpixeldungeon.items.weapon.enchantments.Kinetic;
+import com.shatteredpixel.shatteredpixeldungeon.items.weapon.enchantments.Lucky;
+import com.shatteredpixel.shatteredpixeldungeon.items.weapon.enchantments.Projecting;
+import com.shatteredpixel.shatteredpixeldungeon.items.weapon.enchantments.Shocking;
+import com.shatteredpixel.shatteredpixeldungeon.items.weapon.enchantments.Unstable;
+import com.shatteredpixel.shatteredpixeldungeon.items.weapon.enchantments.Vampiric;
+import com.shatteredpixel.shatteredpixeldungeon.items.weapon.melee.MeleeWeapon;
+import com.shatteredpixel.shatteredpixeldungeon.levels.Level;
+import com.shatteredpixel.shatteredpixeldungeon.levels.Terrain;
+import com.shatteredpixel.shatteredpixeldungeon.mechanics.Ballistica;
+import com.shatteredpixel.shatteredpixeldungeon.mechanics.ConeAOE;
+import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
+import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene;
+import com.shatteredpixel.shatteredpixeldungeon.sprites.CharSprite;
+import com.shatteredpixel.shatteredpixeldungeon.ui.AttackIndicator;
+import com.shatteredpixel.shatteredpixeldungeon.ui.HeroIcon;
+import com.watabou.noosa.Game;
+import com.watabou.noosa.audio.Sample;
+import com.watabou.utils.Callback;
+import com.watabou.utils.Random;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class ElementalStrike extends ArmorAbility {
+
+ //TODO a few duplicates here (curse duplicates are fine)
+ private static final HashMap, Integer> effectTypes = new HashMap<>();
+ static {
+ effectTypes.put(Blazing.class, MagicMissile.FIRE_CONE);
+ effectTypes.put(Chilling.class, MagicMissile.FROST_CONE);
+ effectTypes.put(Kinetic.class, MagicMissile.FORCE_CONE);
+ effectTypes.put(Shocking.class, MagicMissile.SPARK_CONE);
+ effectTypes.put(Blocking.class, MagicMissile.WARD_CONE);
+ effectTypes.put(Blooming.class, MagicMissile.FOLIAGE_CONE);
+ effectTypes.put(Elastic.class, MagicMissile.FORCE_CONE);
+ effectTypes.put(Lucky.class, MagicMissile.RAINBOW_CONE);
+ effectTypes.put(Projecting.class, MagicMissile.PURPLE_CONE);
+ effectTypes.put(Unstable.class, MagicMissile.RAINBOW_CONE);
+ effectTypes.put(Corrupting.class, MagicMissile.SHADOW_CONE);
+ effectTypes.put(Grim.class, MagicMissile.SHADOW_CONE);
+ effectTypes.put(Vampiric.class, MagicMissile.BLOOD_CONE);
+
+ effectTypes.put(Annoying.class, MagicMissile.SHADOW_CONE);
+ effectTypes.put(Displacing.class, MagicMissile.SHADOW_CONE);
+ effectTypes.put(Dazzling.class, MagicMissile.SHADOW_CONE);
+ effectTypes.put(Explosive.class, MagicMissile.SHADOW_CONE);
+ effectTypes.put(Sacrificial.class, MagicMissile.SHADOW_CONE);
+ effectTypes.put(Wayward.class, MagicMissile.SHADOW_CONE);
+ effectTypes.put(Polarized.class, MagicMissile.SHADOW_CONE);
+ effectTypes.put(Friendly.class, MagicMissile.SHADOW_CONE);
+
+ effectTypes.put(null, MagicMissile.MAGIC_MISS_CONE);
+ }
+
+ {
+ baseChargeUse = 25;
+ }
+
+ @Override
+ public String targetingPrompt() {
+ return Messages.get(this, "prompt");
+ }
+
+ @Override
+ public int targetedPos(Char user, int dst) {
+ return dst;
+ }
+
+ //3 tiles in a 65 degree cone
+ // boostable to 4/5/6/7 tiles in a 70/75/80/85 degree cone
+
+ @Override
+ protected void activate(ClassArmor armor, Hero hero, Integer target) {
+ if (target == null){
+ return;
+ }
+
+ armor.charge -= chargeUse(hero);
+ Item.updateQuickslot();
+
+ Ballistica aim = new Ballistica(hero.pos, target, Ballistica.WONT_STOP);
+
+ int maxDist = 3 + hero.pointsInTalent(Talent.ELEMENTAL_REACH);
+ int dist = Math.min(aim.dist, maxDist);
+
+ ConeAOE cone = new ConeAOE(aim,
+ dist,
+ 65 + 5*hero.pointsInTalent(Talent.ELEMENTAL_REACH),
+ Ballistica.STOP_SOLID | Ballistica.STOP_TARGET);
+
+ KindOfWeapon w = hero.belongings.weapon();
+ Weapon.Enchantment enchantment = ((MeleeWeapon) w).enchantment;
+ Class enchCls = null;
+ if (enchantment != null){
+ enchCls = enchantment.getClass();
+ }
+
+ //cast to cells at the tip, rather than all cells, better performance.
+ for (Ballistica ray : cone.outerRays){
+ ((MagicMissile)hero.sprite.parent.recycle( MagicMissile.class )).reset(
+ effectTypes.get(enchCls),
+ hero.sprite,
+ ray.path.get(ray.dist),
+ null
+ );
+ }
+
+ hero.sprite.attack(target, new Callback() {
+ @Override
+ public void call() {
+
+ Char enemy = Actor.findChar(target);
+
+ if (enemy != null) {
+ if (hero.isCharmedBy(enemy)) {
+ enemy = null;
+ } else if (enemy.alignment == hero.alignment) {
+ enemy = null;
+ } else if (!hero.canAttack(enemy)) {
+ enemy = null;
+ }
+ }
+
+ preAttackEffect(cone, hero, enchantment);
+
+ if (enemy != null){
+ AttackIndicator.target(enemy);
+ if (hero.attack(enemy, 1, 0, Char.INFINITE_ACCURACY)) {
+ Sample.INSTANCE.play(Assets.Sounds.HIT_STRONG);
+ }
+ }
+
+ perCellEffect(cone, enchantment);
+
+ perCharEffect(cone, hero, enemy, enchantment);
+
+ Invisibility.dispel();
+ hero.spendAndNext(hero.attackDelay());
+ }
+ });
+
+ Sample.INSTANCE.play(Assets.Sounds.CHARGEUP);
+ hero.busy();
+
+ }
+
+ //effects that trigger before the attack
+ private void preAttackEffect(ConeAOE cone, Hero hero, Weapon.Enchantment ench){
+
+ int targetsHit = 0;
+ for (Char ch : Actor.chars()){
+ if (ch.alignment == Char.Alignment.ENEMY && cone.cells.contains(ch.pos)){
+ targetsHit++;
+ }
+ }
+
+ if (hero.hasTalent(Talent.DIRECTED_POWER)){
+ float enchBoost = 0.25f * targetsHit * hero.pointsInTalent(Talent.DIRECTED_POWER);
+ Buff.affect(hero, DirectedPowerTracker.class, 0f).enchBoost = enchBoost;
+ }
+
+ float powerMulti = 1f + 0.25f*Dungeon.hero.pointsInTalent(Talent.STRIKING_FORCE);
+
+ //*** Kinetic ***
+ if (ench instanceof Kinetic){
+ if (hero.buff(Kinetic.KineticTracker.class) != null) {
+ storedKineticDamage = hero.buff(Kinetic.KineticTracker.class).conservedDamage;
+ }
+
+ //*** Blocking ***
+ } else if (ench instanceof Blocking){
+ if (targetsHit > 0){
+ Buff.affect(hero, Barrier.class).setShield(Math.round(5f*targetsHit*powerMulti));
+ }
+
+ //*** Vampiric ***
+ } else if (ench instanceof Vampiric){
+ if (targetsHit > 0){
+ int heal = Math.round(2f*targetsHit*powerMulti);
+ heal = Math.min( heal, hero.HT - hero.HP );
+ if (heal > 0){
+ hero.HP += heal;
+ hero.sprite.emitter().start( Speck.factory( Speck.HEALING ), 0.4f, 1 );
+ hero.sprite.showStatus( CharSprite.POSITIVE, Integer.toString( heal ) );
+ }
+ }
+
+ //*** Sacrificial ***
+ } else if (ench instanceof Sacrificial){
+ Buff.affect(hero, Bleeding.class).set(10 * powerMulti);
+ }
+
+ }
+
+ public static class DirectedPowerTracker extends FlavourBuff{
+ public float enchBoost = 0f;
+ }
+
+ public static class ElementalStrikeLuckyTracker extends Buff{};
+
+ private int storedKineticDamage = 0;
+
+ //effects that affect the cells of the environment themselves
+ private void perCellEffect(ConeAOE cone, Weapon.Enchantment ench){
+
+ float powerMulti = 1f + 0.25f*Dungeon.hero.pointsInTalent(Talent.STRIKING_FORCE);
+
+ //*** Blazing ***
+ if (ench instanceof Blazing){
+ for (int cell : cone.cells) {
+ GameScene.add(Blob.seed(cell, Math.round(6 * powerMulti), Fire.class));
+ }
+
+ //*** Chilling ***
+ } else if (ench instanceof Chilling){
+ for (int cell : cone.cells) {
+ GameScene.add(Blob.seed(cell, Math.round(6 * powerMulti), Freezing.class));
+ }
+
+ //*** Shocking ***
+ } else if (ench instanceof Shocking){
+ for (int cell : cone.cells) {
+ GameScene.add(Blob.seed(cell, Math.round(6 * powerMulti), Electricity.class));
+ }
+
+ //*** Blooming ***
+ } else if (ench instanceof Blooming){
+ ArrayList cells = new ArrayList<>(cone.cells);
+ Random.shuffle(cells);
+ int grassToPlace = Math.round(6*powerMulti);
+
+ for (int cell : cells) {
+ int terr = Dungeon.level.map[cell];
+ if (terr == Terrain.EMPTY || terr == Terrain.EMBERS || terr == Terrain.EMPTY_DECO ||
+ terr == Terrain.GRASS) {
+ if (grassToPlace > 0){
+ Level.set(cell, Terrain.HIGH_GRASS);
+ grassToPlace--;
+ } else {
+ Level.set(cell, Terrain.GRASS);
+ }
+ GameScene.updateMap( cell );
+ }
+ }
+ Dungeon.observe();
+ }
+ }
+
+ //effects that affect the characters within the cone AOE
+ private void perCharEffect(ConeAOE cone, Hero hero, Char primaryTarget, Weapon.Enchantment ench) {
+
+ float powerMulti = 1f + 0.25f * Dungeon.hero.pointsInTalent(Talent.STRIKING_FORCE);
+
+ ArrayList affected = new ArrayList<>();
+
+ for (Char ch : Actor.chars()) {
+ if (ch.alignment != Char.Alignment.ALLY && cone.cells.contains(ch.pos)) {
+ affected.add(ch);
+ }
+ }
+
+ //*** no enchantment ***
+ if (ench == null) {
+ for (Char ch : affected){
+ ch.damage(Math.round(powerMulti*Random.NormalIntRange(5, 10)), ElementalStrike.this);
+ }
+
+ //*** Kinetic ***
+ } else if (ench instanceof Kinetic){
+ for (Char ch : affected){
+ if (ch != primaryTarget) {
+ ch.damage(Math.round(storedKineticDamage * 0.33f * powerMulti), ench);
+ }
+ }
+
+ //*** Blooming ***
+ } else if (ench instanceof Blooming){
+ for (Char ch : affected){
+ Buff.affect(ch, Roots.class, Math.round(5f*powerMulti));
+ }
+
+ //*** Elastic ***
+ } else if (ench instanceof Elastic){
+ //TODO sort affected by distance first? So further ones get knocked back first
+ for (Char ch : affected){
+ Ballistica aim = new Ballistica(hero.pos, ch.pos, Ballistica.WONT_STOP);
+ int knockback = Math.round(4*powerMulti);
+ WandOfBlastWave.throwChar(ch,
+ new Ballistica(ch.pos, aim.collisionPos, Ballistica.MAGIC_BOLT),
+ knockback,
+ true,
+ true,
+ ElementalStrike.this.getClass());
+ }
+
+ //*** Lucky ***
+ } else if (ench instanceof Lucky){
+ for (Char ch : affected){
+ if (Random.Float() < 0.1f*powerMulti &&
+ ch.buff(ElementalStrikeLuckyTracker.class) == null) {
+ Dungeon.level.drop(Lucky.genLoot(), ch.pos).sprite.drop();
+ Lucky.showFlare(ch.sprite);
+ Buff.affect(ch, ElementalStrikeLuckyTracker.class);
+ }
+ }
+
+ //*** Projecting ***
+ } else if (ench instanceof Projecting){
+ for (Char ch : affected){
+ if (ch != primaryTarget) {
+ ch.damage(Math.round(hero.damageRoll() * 0.25f * powerMulti), ench);
+ }
+ }
+
+ //*** Unstable ***
+ } else if (ench instanceof Unstable){
+ KindOfWeapon w = hero.belongings.weapon();
+ if (w instanceof Weapon) {
+ for (Char ch : affected){
+ if (ch != primaryTarget) {
+ ench.proc((Weapon) w, hero, ch, w.damageRoll(hero));
+ }
+ }
+ }
+
+ //*** Corrupting ***
+ } else if (ench instanceof Corrupting){
+ for (Char ch : affected){
+ if (ch != primaryTarget
+ && !ch.isImmune(Corruption.class)
+ && ch.buff(Corruption.class) == null
+ && ch instanceof Mob
+ && ch.isAlive()) {
+ float hpMissing = 1f - (ch.HP / (float)ch.HT);
+ if (Random.Float() < 0.2f*powerMulti*hpMissing){
+ Corruption.corruptionHeal(ch);
+ AllyBuff.affectAndLoot((Mob) ch, hero, Corruption.class);
+ }
+ }
+ }
+
+ //*** Grim ***
+ } else if (ench instanceof Grim){
+ for (Char ch : affected){
+ if (ch != primaryTarget) {
+ float hpMissing = 1f - (ch.HP / (float)ch.HT);
+ if (Random.Float() < 0.3f*powerMulti*hpMissing){
+ ch.damage( ch.HP, Grim.class );
+ ch.sprite.emitter().burst( ShadowParticle.UP, 5 );
+ }
+ }
+ }
+
+ //*** Annoying ***
+ } else if (ench instanceof Annoying){
+ for (Char ch : affected){
+ if (Random.Float() < 0.1f*powerMulti){
+ //TODO totally should add a bit of dialogue here
+ Buff.affect(ch, Amok.class, 5f);
+ }
+ }
+
+ //*** Displacing ***
+ } else if (ench instanceof Displacing){
+ for (Char ch : affected){
+ if (Random.Float() < 0.5f*powerMulti){
+ int oldpos = ch.pos;
+ if (ScrollOfTeleportation.teleportChar(ch)){
+ if (Dungeon.level.heroFOV[oldpos]) {
+ CellEmitter.get( oldpos ).start( Speck.factory( Speck.LIGHT ), 0.2f, 3 );
+ }
+
+ if (ch instanceof Mob && ((Mob) ch).state == ((Mob) ch).HUNTING){
+ ((Mob) ch).state = ((Mob) ch).WANDERING;
+ }
+ }
+ }
+ }
+
+ //*** Dazzling ***
+ } else if (ench instanceof Dazzling){
+ for (Char ch : affected){
+ if (Random.Float() < 0.5f*powerMulti){
+ Buff.affect(ch, Blindness.class, 5f);
+ }
+ }
+
+ //*** Explosive ***
+ } else if (ench instanceof Explosive){
+ if (Random.Float() < 0.5f*powerMulti){
+ Char exploding = Random.element(affected);
+ if (exploding != null) new Bomb.MagicalBomb().explode(exploding.pos);
+ }
+
+ //*** Sacrificial ***
+ } else if (ench instanceof Sacrificial){
+ for (Char ch : affected){
+ Buff.affect(ch, Bleeding.class).set(10f*powerMulti);
+ }
+
+ //*** Wayward ***
+ } else if (ench instanceof Wayward){
+ for (Char ch : affected){
+ if (Random.Float() < 0.5f*powerMulti){
+ Buff.affect(ch, Hex.class, 5f);
+ }
+ }
+
+ //*** Polarized ***
+ } else if (ench instanceof Polarized){
+ for (Char ch : affected){
+ if (Random.Float() < 0.5f*powerMulti){
+ ch.damage(Random.NormalIntRange(20, 30), ElementalStrike.this);
+ }
+ }
+
+ //*** Friendly ***
+ } else if (ench instanceof Friendly){
+ for (Char ch : affected){
+ if (Random.Float() < 0.5f*powerMulti){
+ Buff.affect(ch, Charm.class, 5f).target = hero;
+ }
+ }
+ }
+
+ }
+
+ @Override
+ public String desc() {
+ String desc = Messages.get(this, "desc");
+ if (Game.scene() instanceof GameScene){
+ KindOfWeapon w = Dungeon.hero.belongings.weapon();
+ if (w instanceof MeleeWeapon && ((MeleeWeapon) w).enchantment != null){
+ desc += "\n\n" + Messages.get(((MeleeWeapon) w).enchantment, "elestrike_desc");
+ } else {
+ desc += "\n\n" + Messages.get(this, "generic_desc");
+ }
+ } else {
+ desc += "\n\n" + Messages.get(this, "generic_desc");
+ }
+ desc += "\n\n" + Messages.get(this, "cost", (int)baseChargeUse);
+ return desc;
+ }
+
+ @Override
+ public int icon() {
+ return HeroIcon.ELEMENTAL_STRIKE;
+ }
+
+ @Override
+ public Talent[] talents() {
+ return new Talent[]{Talent.ELEMENTAL_REACH, Talent.STRIKING_FORCE, Talent.DIRECTED_POWER, Talent.HEROIC_ENERGY};
+ }
+
+}
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 b51e05b42..30808b087 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
@@ -28,6 +28,8 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Hex;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.MagicalSleep;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Vulnerable;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Weakness;
+import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.duelist.ElementalStrike;
+import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.mage.ElementalBlast;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.mage.WarpBeacon;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.DM100;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Eye;
@@ -81,6 +83,7 @@ public class AntiMagic extends Armor.Glyph {
RESISTS.add( ScrollOfPsionicBlast.class );
RESISTS.add( ScrollOfTeleportation.class );
+ RESISTS.add( ElementalBlast.class );
RESISTS.add( CursedWand.class );
RESISTS.add( WandOfBlastWave.class );
RESISTS.add( WandOfDisintegration.class );
@@ -93,6 +96,7 @@ public class AntiMagic extends Armor.Glyph {
RESISTS.add( WandOfTransfusion.class );
RESISTS.add( WandOfWarding.Ward.class );
+ RESISTS.add( ElementalStrike.class );
RESISTS.add( Blazing.class );
RESISTS.add( Shocking.class );
RESISTS.add( Grim.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 294e7d66b..a461b4977 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
@@ -28,6 +28,7 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Berserk;
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.abilities.duelist.ElementalStrike;
import com.shatteredpixel.shatteredpixeldungeon.items.Item;
import com.shatteredpixel.shatteredpixeldungeon.items.KindOfWeapon;
import com.shatteredpixel.shatteredpixeldungeon.items.rings.RingOfArcana;
@@ -392,6 +393,12 @@ abstract public class Weapon extends KindOfWeapon {
if (attacker.buff(RunicBlade.RunicSlashTracker.class) != null){
multi += 2f;
+ attacker.buff(RunicBlade.RunicSlashTracker.class).detach();
+ }
+
+ if (attacker.buff(ElementalStrike.DirectedPowerTracker.class) != null){
+ multi += attacker.buff(ElementalStrike.DirectedPowerTracker.class).enchBoost;
+ attacker.buff(ElementalStrike.DirectedPowerTracker.class).detach();
}
if (attacker.buff(Talent.SpiritBladesTracker.class) != null
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 d1e76d185..557b017e7 100644
--- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/ui/HeroIcon.java
+++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/ui/HeroIcon.java
@@ -62,7 +62,7 @@ public class HeroIcon extends Image {
public static final int NATURES_POWER = 26;
public static final int SPIRIT_HAWK = 27;
public static final int CHALLENGE = 28;
- public static final int DUELIST_2 = 29;
+ public static final int ELEMENTAL_STRIKE= 29;
public static final int DUELIST_3 = 30;
public static final int RATMOGRIFY = 33;