v2.0.0: base implementation for Feint ability

This commit is contained in:
Evan Debenham
2023-03-03 13:12:19 -05:00
parent 31b3e36f87
commit 36edb9c87d
7 changed files with 285 additions and 39 deletions

View File

@@ -489,6 +489,12 @@ 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.duelist.feint.name=feint
actors.hero.abilities.duelist.feint.prompt=Choose a location to dash to
actors.hero.abilities.duelist.feint.too_far=That location is not adjacent to you
actors.hero.abilities.duelist.feint.bad_location=You can't move to that location
actors.hero.abilities.duelist.feint.short_desc=The Duelist _feints_, faking an attack while dashing to an adjacent tile. Enemies will attack her afterimage, leaving them open to a counterattack.
actors.hero.abilities.duelist.feint.desc=The Duelist fakes an attack while dashing to an adjacent tile, leaving behind a momentary afterimage of herself. Enemies that were attacking the Duelist will attack her afterimage instead.\n\nEnemies that attack the afterimage become confused, which cancels their next action and leaves them open to a surprise attack.
actors.hero.abilities.ratmogrify.name=ratmogrify
actors.hero.abilities.ratmogrify.cant_transform=You can't ratmogrify that!
@@ -904,7 +910,12 @@ actors.hero.talent.striking_force.desc=_+1:_ The power of elemental strike's eff
actors.hero.talent.directed_power.title=directed power
actors.hero.talent.directed_power.desc=_+1:_ The direct attack from elemental strike gains _+30% 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 _+60% 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 _+90% 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 _+120% enchantment power_ for each enemy in range of elemental strike's effect, including the attack target.
#third armor ability
actors.hero.talent.feigned_retreat.title=feigned retreat
actors.hero.talent.feigned_retreat.desc=_+1:_ If an enemy attacks the Duelist's afterimage, she gains _2 turns_ of haste.\n\n_+2:_ If an enemy attacks the Duelist's afterimage, she gains _4 turns_ of haste.\n\n_+3:_ If an enemy attacks the Duelist's afterimage, she gains _6 turns_ of haste.\n\n_+4:_ If an enemy attacks the Duelist's afterimage, she gains _8 turns_ of haste.
actors.hero.talent.expose_weakness.title=expose weakness
actors.hero.talent.expose_weakness.desc=_+1:_ Enemies that attack the Duelist's afterimage become vulnerable for _1 turn_.\n\n_+2:_ Enemies that attack the Duelist's afterimage become vulnerable for _2 turns_.\n\n_+3:_ Enemies that attack the Duelist's afterimage become vulnerable for _3 turns_.\n\n_+1:_ Enemies that attack the Duelist's afterimage become vulnerable for _4 turns_.
actors.hero.talent.lasting_image.title=TODO
actors.hero.talent.lasting_image.desc=specific mechanics TBD
#universal
actors.hero.talent.heroic_energy.title=heroic energy

View File

@@ -165,7 +165,7 @@ public enum Talent {
//Elemental Strike T4
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),
FEIGNED_RETREAT(151, 4), EXPOSE_WEAKNESS(152, 4), LASTING_IMAGE(153, 4),
//universal T4
HEROIC_ENERGY(26, 4), //See icon() and title() for special logic for this one

View File

@@ -0,0 +1,244 @@
/*
* 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 <http://www.gnu.org/licenses/>
*/
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.buffs.BlobImmunity;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.FlavourBuff;
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.levels.Terrain;
import com.shatteredpixel.shatteredpixeldungeon.levels.features.Door;
import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene;
import com.shatteredpixel.shatteredpixeldungeon.sprites.CharSprite;
import com.shatteredpixel.shatteredpixeldungeon.sprites.MirrorSprite;
import com.shatteredpixel.shatteredpixeldungeon.ui.HeroIcon;
import com.shatteredpixel.shatteredpixeldungeon.ui.TargetHealthIndicator;
import com.shatteredpixel.shatteredpixeldungeon.utils.GLog;
import com.watabou.noosa.audio.Sample;
import com.watabou.noosa.tweeners.AlphaTweener;
import com.watabou.noosa.tweeners.Delayer;
import com.watabou.utils.Callback;
public class Feint extends ArmorAbility {
{
baseChargeUse = 0;
}
@Override
public int icon() {
return HeroIcon.FEINT;
}
@Override
public String targetingPrompt() {
return Messages.get(this, "prompt");
}
@Override
public int targetedPos(Char user, int dst) {
return dst;
}
@Override
protected void activate(ClassArmor armor, Hero hero, Integer target) {
if (target == null){
return;
}
if (!Dungeon.level.adjacent(hero.pos, target)){
GLog.w(Messages.get(this, "too_far"));
return;
}
if (!Dungeon.level.passable[target] || Actor.findChar(target) != null){
GLog.w(Messages.get(this, "bad_location"));
return;
}
hero.busy();
Sample.INSTANCE.play(Assets.Sounds.MISS);
hero.sprite.jump(hero.pos, target, 0, 0.1f, new Callback() {
@Override
public void call() {
if (Dungeon.level.map[hero.pos] == Terrain.OPEN_DOOR) {
Door.leave( hero.pos );
}
hero.pos = target;
Dungeon.level.occupyCell(hero);
hero.spendAndNext(1f);
}
});
AfterImage image = new AfterImage();
image.pos = hero.pos;
GameScene.add(image, 1);
int imageAttackPos;
Char enemyTarget = TargetHealthIndicator.instance.target();
if (enemyTarget != null && enemyTarget.alignment == Char.Alignment.ENEMY){
imageAttackPos = enemyTarget.pos;
} else {
imageAttackPos = image.pos + (image.pos - target);
}
//do a purely visual attack
hero.sprite.parent.add(new Delayer(0f){
@Override
protected void onComplete() {
image.sprite.attack(imageAttackPos, new Callback() {
@Override
public void call() {
//do nothing, attack is purely visual
}
});
}
});
for (Mob m : Dungeon.level.mobs.toArray( new Mob[0] )){
if (m.focusingHero() ||
(m.alignment == Char.Alignment.ENEMY && m.state != m.PASSIVE && Dungeon.level.distance(m.pos, image.pos) <= 2)){
m.aggro(image);
}
}
armor.charge -= chargeUse(hero);
Item.updateQuickslot();
}
@Override
public Talent[] talents() {
return new Talent[]{Talent.FEIGNED_RETREAT, Talent.EXPOSE_WEAKNESS, Talent.LASTING_IMAGE, Talent.HEROIC_ENERGY};
}
public static class AfterImage extends Mob {
{
spriteClass = AfterImageSprite.class;
defenseSkill = 0;
properties.add(Property.IMMOVABLE);
alignment = Alignment.ALLY;
state = PASSIVE;
HP = HT = 1;
//fades just before the hero's next action
actPriority = Actor.HERO_PRIO+1;
}
@Override
public boolean canInteract(Char c) {
return false;
}
@Override
protected boolean act() {
destroy();
sprite.die();
return true;
}
@Override
public void damage( int dmg, Object src ) {
}
@Override
public int defenseSkill(Char enemy) {
if (enemy instanceof Mob){
((Mob) enemy).clearEnemy();
}
Buff.affect(enemy, FeintConfusion.class, 1);
if (enemy.sprite != null) enemy.sprite.showLost();
return 0;
}
@Override
public int defenseProc(Char enemy, int damage) {
if (enemy instanceof Mob){
((Mob) enemy).clearEnemy();
}
Buff.affect(enemy, FeintConfusion.class, 1f);
if (enemy.sprite != null) enemy.sprite.showLost();
return super.defenseProc(enemy, damage);
}
@Override
public void add( Buff buff ) {
}
{
immunities.addAll(new BlobImmunity().immunities());
}
@Override
public CharSprite sprite() {
CharSprite s = super.sprite();
((AfterImageSprite)s).updateArmor();
return s;
}
public static class FeintConfusion extends FlavourBuff {
}
public static class AfterImageSprite extends MirrorSprite {
@Override
public void updateArmor() {
updateArmor(6); //we can assume heroic armor
}
@Override
public void resetColor() {
super.resetColor();
alpha(0.6f);
}
@Override
public void die() {
//don't interrupt current animation to start fading
//this ensures the face attack animation plays
if (parent != null) {
parent.add( new AlphaTweener( this, 0, 3f ) {
@Override
protected void onComplete() {
AfterImageSprite.this.killAndErase();
}
} );
}
}
}
}
}

View File

@@ -50,6 +50,7 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.HeroClass;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.HeroSubClass;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Talent;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.duelist.Feint;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.DirectableAlly;
import com.shatteredpixel.shatteredpixeldungeon.effects.CellEmitter;
import com.shatteredpixel.shatteredpixeldungeon.effects.Speck;
@@ -225,6 +226,13 @@ public abstract class Mob extends Char {
boolean enemyInFOV = enemy != null && enemy.isAlive() && fieldOfView[enemy.pos] && enemy.invisible <= 0;
//prevents action, but still updates enemy seen status
if (buff(Feint.AfterImage.FeintConfusion.class) != null){
enemySeen = enemyInFOV;
spend( TICK );
return true;
}
return state.act( enemyInFOV, justAlerted );
}
@@ -690,6 +698,12 @@ public abstract class Mob extends Char {
state = HUNTING;
}
}
public void clearEnemy(){
enemy = null;
enemySeen = false;
if (state == HUNTING) state = WANDERING;
}
public boolean isTargeting( Char ch){
return enemy == ch;

View File

@@ -25,6 +25,7 @@ import com.shatteredpixel.shatteredpixeldungeon.Dungeon;
import com.shatteredpixel.shatteredpixeldungeon.actors.Char;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.MirrorImage;
import com.watabou.noosa.TextureFilm;
import com.watabou.utils.PointF;
public class MirrorSprite extends MobSprite {
@@ -42,6 +43,15 @@ public class MirrorSprite extends MobSprite {
@Override
public void link( Char ch ) {
super.link( ch );
updateArmor();
}
@Override
public void bloodBurstA(PointF from, int damage) {
//do nothing
}
public void updateArmor(){
updateArmor( ((MirrorImage)ch).armTier );
}

View File

@@ -21,49 +21,16 @@
package com.shatteredpixel.shatteredpixeldungeon.sprites;
import com.shatteredpixel.shatteredpixeldungeon.Dungeon;
import com.shatteredpixel.shatteredpixeldungeon.actors.Char;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.PrismaticImage;
import com.watabou.noosa.Game;
import com.watabou.noosa.TextureFilm;
public class PrismaticSprite extends MobSprite {
private static final int FRAME_WIDTH = 12;
private static final int FRAME_HEIGHT = 15;
public PrismaticSprite() {
super();
texture( Dungeon.hero.heroClass.spritesheet() );
updateArmor( 0 );
idle();
}
public class PrismaticSprite extends MirrorSprite {
@Override
public void link( Char ch ) {
super.link( ch );
public void updateArmor() {
updateArmor( ((PrismaticImage)ch).armTier );
}
public void updateArmor( int tier ) {
TextureFilm film = new TextureFilm( HeroSprite.tiers(), tier, FRAME_WIDTH, FRAME_HEIGHT );
idle = new Animation( 1, true );
idle.frames( film, 0, 0, 0, 1, 0, 0, 1, 1 );
run = new Animation( 20, true );
run.frames( film, 2, 3, 4, 5, 6, 7 );
die = new Animation( 20, false );
die.frames( film, 0 );
attack = new Animation( 15, false );
attack.frames( film, 13, 14, 15, 0 );
idle();
}
@Override
public void update() {
super.update();

View File

@@ -63,7 +63,7 @@ public class HeroIcon extends Image {
public static final int SPIRIT_HAWK = 27;
public static final int CHALLENGE = 28;
public static final int ELEMENTAL_STRIKE= 29;
public static final int DUELIST_3 = 30;
public static final int FEINT = 30;
public static final int RATMOGRIFY = 31;
public HeroIcon(HeroSubClass subCls){