From 3f5f95cf28789d2f2220b66ef8bb1d27a5e5faf9 Mon Sep 17 00:00:00 2001 From: Evan Debenham Date: Sun, 26 Feb 2023 23:23:15 -0500 Subject: [PATCH] v2.0.0: implemented the focus and dash monk abilities --- .../assets/messages/actors/actors.properties | 15 +- .../shatteredpixeldungeon/actors/Char.java | 11 +- .../actors/buffs/MonkEnergy.java | 159 +++++++++++++----- .../actors/hero/Hero.java | 9 + .../actors/mobs/RipperDemon.java | 2 +- 5 files changed, 143 insertions(+), 53 deletions(-) diff --git a/core/src/main/assets/messages/actors/actors.properties b/core/src/main/assets/messages/actors/actors.properties index 67eb51195..caf52ff94 100644 --- a/core/src/main/assets/messages/actors/actors.properties +++ b/core/src/main/assets/messages/actors/actors.properties @@ -274,11 +274,16 @@ actors.buffs.monkenergy.name=energy actors.buffs.monkenergy.desc=As she defeats enemies, the monk gains energy that she can use on a variety of abilities. Most enemies grant 1 energy when defeated, and the Monk does not lose energy over time.\n\nCurrent energy: %1$d/%2$d. actors.buffs.monkenergy.desc_cooldown=The monk has recently used an ability, and must wait before using another.\n\nCurrent cooldown: %d turns. actors.buffs.monkenergy$monkability$flurry.name=flurry of blows -actors.buffs.monkenergy$monkability$flurry.desc=An instant strike that deals X-Y damage and ignores armor. This ability has no cooldown if the Monk just attacked normally or with a weapon ability. -actors.buffs.monkenergy$monkability$dash.name=dash -actors.buffs.monkenergy$monkability$dash.desc=An instant dash up to 2 tiles away. This ability can go over hazards, but not through enemies or walls. +actors.buffs.monkenergy$monkability$flurry.desc=An instant strike that deals %1$d-%2$d damage and ignores armor. This ability has no cooldown if the Monk just attacked normally or with a weapon ability. actors.buffs.monkenergy$monkability$focus.name=focus -actors.buffs.monkenergy$monkability$focus.desc=The monk takes a turn to focus, letting them dodge the next physical attack made against them within 20 turns. +actors.buffs.monkenergy$monkability$focus.desc=The monk takes a turn to focus, letting her parry the next physical attack made against her within 30 turns. +actors.buffs.monkenergy$monkability$focus$focusbuff.name=focused +actors.buffs.monkenergy$monkability$focus$focusbuff.desc=The monk is focused on her surroundings, anticipating the next physical attack made against her. While focused, she is garunteed to parry the next incoming physical attack.\n\nTurns remaining: %s. +actors.buffs.monkenergy$monkability$dash.name=dash +actors.buffs.monkenergy$monkability$dash.prompt=Choose a location +actors.buffs.monkenergy$monkability$dash.too_far=That location is too far away. +actors.buffs.monkenergy$monkability$dash.blocked=There is something blocking that location. +actors.buffs.monkenergy$monkability$dash.desc=An instant dash up to 2 tiles away. This ability can go over hazards, but not through enemies or walls. actors.buffs.monkenergy$monkability$dragonkick.name=dragon kick actors.buffs.monkenergy$monkability$dragonkick.desc=A devastating kick that deals X-Y damage and ignores armor. The force of the kick is so strong that the target is knocked away and paralyzed for 10 turns. actors.buffs.monkenergy$monkability$meditate.name=meditate @@ -1181,7 +1186,7 @@ actors.mobs.monk.def_verb=blocked actors.mobs.monk.parried=parried actors.mobs.monk.desc=These monks are fanatics, who have devoted themselves to protecting their king through physical might. So great is their devotion that they have totally surrendered their minds to their king, and now roam the dwarvern city like mindless zombies.\n\nMonks rely solely on the art of hand-to-hand combat, and are able to use their unarmed fists both for offense and defense. When they become focused, monks will parry the next physical attack used against them, even if it was otherwise guaranteed to hit. Monks build focus more quickly while on the move, and more slowly when in direct combat. actors.mobs.monk$focus.name=focused -actors.mobs.monk$focus.desc=This monk is perfectly honed in on their target, and seem to be anticipating their moves before they make them.\n\nWhile focused, the next physical attack made against their character is guaranteed to miss, no matter what circumstances there are. Parrying this attack will spend the monk's focus, and they will need to build it up again to parry another attack. Monks build focus more quickly while they are moving. +actors.mobs.monk$focus.desc=This monk is perfectly honed in on their target, and seem to be anticipating their moves before they make them.\n\nWhile focused, the next physical attack made against this character is guaranteed to miss, no matter what circumstances there are. Parrying this attack will spend the monk's focus, and they will need to build it up again to parry another attack. Monks build focus more quickly while they are moving. actors.mobs.piranha.name=giant piranha actors.mobs.piranha.desc=These carnivorous fish are not natural inhabitants of underground pools. They were bred specifically to protect flooded treasure vaults. diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/Char.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/Char.java index ccf4eef8a..d348bd826 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/Char.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/Char.java @@ -54,6 +54,7 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.LifeLink; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.LostInventory; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.MagicalSleep; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Momentum; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.MonkEnergy; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Ooze; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Paralysis; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Poison; @@ -323,7 +324,7 @@ public abstract class Char extends Actor { return false; - } else if (hit( this, enemy, accMulti )) { + } else if (hit( this, enemy, accMulti, false )) { int dr = Math.round(enemy.drRoll() * AscensionChallenge.statModifier(enemy)); @@ -488,10 +489,10 @@ public abstract class Char extends Actor { public static int INFINITE_EVASION = 1_000_000; final public static boolean hit( Char attacker, Char defender, boolean magic ) { - return hit(attacker, defender, magic ? 2f : 1f); + return hit(attacker, defender, magic ? 2f : 1f, magic); } - public static boolean hit( Char attacker, Char defender, float accMulti ) { + public static boolean hit( Char attacker, Char defender, float accMulti, boolean magic ) { float acuStat = attacker.attackSkill( defender ); float defStat = defender.defenseSkill( attacker ); @@ -500,6 +501,10 @@ public abstract class Char extends Actor { acuStat = INFINITE_ACCURACY; } + if (defender.buff(MonkEnergy.MonkAbility.Focus.FocusBuff.class) != null && !magic){ + defStat = INFINITE_EVASION; + } + //if accuracy or evasion are large enough, treat them as infinite. //note that infinite evasion beats infinite accuracy if (defStat >= INFINITE_EVASION){ diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/buffs/MonkEnergy.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/buffs/MonkEnergy.java index 4f5632128..6bf873ba5 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/buffs/MonkEnergy.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/buffs/MonkEnergy.java @@ -23,21 +23,26 @@ package com.shatteredpixel.shatteredpixeldungeon.actors.buffs; 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.hero.Hero; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.HeroSubClass; import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Ghoul; import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mob; +import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Monk; import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.RipperDemon; import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Wraith; import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.YogDzewa; +import com.shatteredpixel.shatteredpixeldungeon.effects.Speck; import com.shatteredpixel.shatteredpixeldungeon.levels.Terrain; import com.shatteredpixel.shatteredpixeldungeon.levels.features.Door; +import com.shatteredpixel.shatteredpixeldungeon.mechanics.Ballistica; import com.shatteredpixel.shatteredpixeldungeon.messages.Messages; import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene; import com.shatteredpixel.shatteredpixeldungeon.ui.ActionIndicator; import com.shatteredpixel.shatteredpixeldungeon.ui.BuffIndicator; import com.shatteredpixel.shatteredpixeldungeon.ui.HeroIcon; +import com.shatteredpixel.shatteredpixeldungeon.utils.GLog; import com.shatteredpixel.shatteredpixeldungeon.windows.WndMonkAbilities; import com.watabou.noosa.Image; import com.watabou.noosa.audio.Sample; @@ -141,6 +146,7 @@ public class MonkEnergy extends Buff implements ActionIndicator.Action { if (energy > 0 && cooldown == 0){ ActionIndicator.setAction(this); } + BuffIndicator.refreshHero(); } //10 at base, 20 at level 30 @@ -148,6 +154,14 @@ public class MonkEnergy extends Buff implements ActionIndicator.Action { return Math.max(10, 5 + Dungeon.hero.lvl/2); } + public void abilityUsed( MonkAbility abil ){ + energy -= abil.energyCost(); + cooldown = abil.cooldown(); + if (cooldown > 0 || energy < 1){ + ActionIndicator.clearAction(this); + } + } + @Override public String actionName() { return "TODO"; @@ -167,8 +181,8 @@ public class MonkEnergy extends Buff implements ActionIndicator.Action { public static MonkAbility[] abilities = new MonkAbility[]{ new Flurry(), - new Dash(), new Focus(), + new Dash(), new DragonKick(), new Meditate() }; @@ -203,31 +217,15 @@ public class MonkEnergy extends Buff implements ActionIndicator.Action { } @Override - public String targetingPrompt() { - return "choose a location"; - } - - @Override - public void doAbility(Hero hero, Integer target) { - //TODO - } - } - - public static class Dash extends MonkAbility { - - @Override - public int energyCost() { - return 2; - } - - @Override - public int cooldown() { - return 3; //extra turn as no time is spend dashing + public String desc() { + //double hero unarmed damage + //TODO maybe two hits at regular unarmed damage instead? + return Messages.get(this, "desc", 2, 2*(Dungeon.hero.STR()-8)); } @Override public String targetingPrompt() { - return "choose a location"; + return "choose a target"; } @Override @@ -236,10 +234,103 @@ public class MonkEnergy extends Buff implements ActionIndicator.Action { return; } - //TODO check conditions + //TODO check for target viability + + //TODO add a buff that forces melee only (and no RoF!) + + //check for can attack + + //clear buff if can't + + //do attack logic + } + } + + public static class Focus extends MonkAbility { + + @Override + public int energyCost() { + return 2; + } + + @Override + public int cooldown() { + return 4; + } + + @Override + public void doAbility(Hero hero, Integer target) { + Buff.prolong(hero, FocusBuff.class, 30f); + + Buff.affect(hero, MonkEnergy.class).abilityUsed(this); + hero.spendAndNext(1f); + } + + public static class FocusBuff extends FlavourBuff { + + { + type = buffType.POSITIVE; + announced = true; + } + + @Override + public int icon() { + return BuffIndicator.MIND_VISION; + } + + @Override + public void tintIcon(Image icon) { + icon.hardlight(0.25f, 1.5f, 1f); + } + + @Override + public float iconFadePercent() { + return Math.max(0, (30 - visualcooldown()) / 30); + } + } + + } + + public static class Dash extends MonkAbility { + + @Override + public int energyCost() { + return 3; + } + + @Override + public int cooldown() { + return 3; //1 less turn as no time is spent dashing + } + + @Override + public String targetingPrompt() { + return Messages.get(this, "prompt"); + } + + @Override + public void doAbility(Hero hero, Integer target) { + if (target == null || target == -1){ + return; + } + + if (Dungeon.level.distance(hero.pos, target) > 3){ + GLog.w(Messages.get(this, "too_far")); + return; + } + + Ballistica dash = new Ballistica(hero.pos, target, Ballistica.PROJECTILE); + + if (!dash.collisionPos.equals(target) + || Actor.findChar(target) != null + || (Dungeon.level.solid[target] && !Dungeon.level.passable[target])){ + GLog.w(Messages.get(this, "blocked")); + return; + } hero.busy(); Sample.INSTANCE.play(Assets.Sounds.MISS); + hero.sprite.emitter().start(Speck.factory(Speck.JET), 0.01f, Math.round(4 + 2*Dungeon.level.trueDistance(hero.pos, target))); hero.sprite.jump(hero.pos, target, 0, 0.1f, new Callback() { @Override public void call() { @@ -252,30 +343,10 @@ public class MonkEnergy extends Buff implements ActionIndicator.Action { } }); - //TODO decrement energy - + Buff.affect(hero, MonkEnergy.class).abilityUsed(this); } } - public static class Focus extends MonkAbility { - - @Override - public int energyCost() { - return 3; - } - - @Override - public int cooldown() { - return 3; - } - - @Override - public void doAbility(Hero hero, Integer target) { - //TODO - } - - } - public static class DragonKick extends MonkAbility { @Override diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/Hero.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/Hero.java index 9eada537f..b97735194 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/Hero.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/Hero.java @@ -54,6 +54,7 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Levitation; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.LostInventory; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.MindVision; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Momentum; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.MonkEnergy; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Paralysis; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.PhysicalEmpower; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Recharging; @@ -540,6 +541,14 @@ public class Hero extends Char { return Messages.get(Monk.class, "parried"); } + if (buff(MonkEnergy.MonkAbility.Focus.FocusBuff.class) != null){ + buff(MonkEnergy.MonkAbility.Focus.FocusBuff.class).detach(); + if (sprite != null && sprite.visible) { + Sample.INSTANCE.play(Assets.Sounds.HIT_PARRY, 1, Random.Float(0.96f, 1.05f)); + } + return Messages.get(Monk.class, "parried"); + } + if (buff(RoundShield.GuardTracker.class) != null){ buff(RoundShield.GuardTracker.class).detach(); Sample.INSTANCE.play(Assets.Sounds.HIT_PARRY, 1, Random.Float(0.96f, 1.05f)); diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/RipperDemon.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/RipperDemon.java index 9f379dee0..2990d4c4d 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/RipperDemon.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/RipperDemon.java @@ -176,7 +176,7 @@ public class RipperDemon extends Mob { public void call() { if (leapVictim != null && alignment != leapVictim.alignment){ - if (hit(RipperDemon.this, leapVictim, Char.INFINITE_ACCURACY)) { + if (hit(RipperDemon.this, leapVictim, Char.INFINITE_ACCURACY, false)) { Buff.affect(leapVictim, Bleeding.class).set(0.75f * damageRoll()); leapVictim.sprite.flash(); Sample.INSTANCE.play(Assets.Sounds.HIT);