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;