v0.8.1: balance/design improvements to assassin and sniper:

- sniper's mark now lasts 4 turns, up from 2
- further increased distance-based damage scaling on sniper shot
- assassin preparation now maxes at 11 turns, and becomes stronger faster
- preparation now executes enemies below a health threshold, rather than dealing more damage at low enemy HP.
This commit is contained in:
Evan Debenham
2020-06-08 17:53:58 -04:00
parent 093671ed3b
commit fa4d0e908d
5 changed files with 44 additions and 53 deletions
@@ -232,9 +232,7 @@ actors.buffs.poison.desc=Poison works its way through the body, slowly impairing
actors.buffs.preparation.name=Preparation actors.buffs.preparation.name=Preparation
actors.buffs.preparation.desc=The Assassin is waiting patiently, preparing to strike from the shadows. actors.buffs.preparation.desc=The Assassin is waiting patiently, preparing to strike from the shadows.
actors.buffs.preparation.desc_dmg=His next attack will do _%d%% bonus damage._ actors.buffs.preparation.desc_dmg=His next attack will deal _%1$d%% bonus damage_, and will execute regular enemies below _%2$d%% health_, or bosses below _%3$d%% health_.
actors.buffs.preparation.desc_dmg_scale=His next attack will do _%d%%-%d%% bonus damage,_ depending on how injured the target is.
actors.buffs.preparation.desc_dmg_instakill=His next attack will _instantly kill_ any non-boss enemy!\n\nOtherwise it will do _%d%%-%d%% bonus damage,_ depending on how injured the target is.
actors.buffs.preparation.desc_dmg_likely=The attack will also be more likely to deal a larger amount of damage. actors.buffs.preparation.desc_dmg_likely=The attack will also be more likely to deal a larger amount of damage.
actors.buffs.preparation.desc_blink=He is able to blink towards an enemy before striking them, with a max distance of _%d._ actors.buffs.preparation.desc_blink=He is able to blink towards an enemy before striking them, with a max distance of _%d._
actors.buffs.preparation.desc_invis_time=The Assassin has been invisible for _%d turns._ actors.buffs.preparation.desc_invis_time=The Assassin has been invisible for _%d turns._
@@ -242,6 +240,7 @@ actors.buffs.preparation.desc_invis_next=His attack will become stronger at _%d
actors.buffs.preparation.prompt=Select a target to attack!\nMax blink distance: %d actors.buffs.preparation.prompt=Select a target to attack!\nMax blink distance: %d
actors.buffs.preparation.no_target=There's nothing to attack there. actors.buffs.preparation.no_target=There's nothing to attack there.
actors.buffs.preparation.out_of_reach=That target is out of reach. actors.buffs.preparation.out_of_reach=That target is out of reach.
actors.buffs.preparation.assassinated=assassinated
actors.buffs.prismaticguard.name=Prismatic Guard actors.buffs.prismaticguard.name=Prismatic Guard
actors.buffs.prismaticguard.desc=You are being guarded by a prismatic image which is currently inactive. When enemies are present the prismatic image will spring to action and protect you!\n\nWhile inactive, the prismatic image will steadily recover from any damage it has taken.\n\nCurrent HP: %d/%d. actors.buffs.prismaticguard.desc=You are being guarded by a prismatic image which is currently inactive. When enemies are present the prismatic image will spring to action and protect you!\n\nWhile inactive, the prismatic image will steadily recover from any damage it has taken.\n\nCurrent HP: %d/%d.
@@ -265,7 +265,7 @@ public abstract class Char extends Actor {
int dmg; int dmg;
Preparation prep = buff(Preparation.class); Preparation prep = buff(Preparation.class);
if (prep != null){ if (prep != null){
dmg = prep.damageRoll(this, enemy); dmg = prep.damageRoll(this);
} else { } else {
dmg = damageRoll(); dmg = damageRoll();
} }
@@ -298,6 +298,11 @@ public abstract class Char extends Actor {
if (buff(FrostImbue.class) != null) if (buff(FrostImbue.class) != null)
buff(FrostImbue.class).proc(enemy); buff(FrostImbue.class).proc(enemy);
if (prep != null && prep.canKO(enemy)){
enemy.die(this);
enemy.sprite.showStatus(CharSprite.NEGATIVE, Messages.get(Preparation.class, "assassinated"));
}
enemy.sprite.bloodBurstA( sprite.center(), effectiveDamage ); enemy.sprite.bloodBurstA( sprite.center(), effectiveDamage );
enemy.sprite.flash(); enemy.sprite.flash();
@@ -54,36 +54,37 @@ public class Preparation extends Buff implements ActionIndicator.Action {
} }
public enum AttackLevel{ public enum AttackLevel{
LVL_1( 1, 0.1f, 0.0f, 1, 0), LVL_1( 1, 0.15f, 0.05f, 1, 1),
LVL_2( 3, 0.2f, 0.0f, 1, 1), LVL_2( 3, 0.30f, 0.15f, 1, 3),
LVL_3( 6, 0.3f, 0.0f, 2, 3), LVL_3( 6, 0.45f, 0.30f, 2, 5),
LVL_4( 11, 0.4f, 0.6f, 2, 5), LVL_4( 11, 0.60f, 0.50f, 3, 7);
LVL_5( 16, 0.5f, 1.0f, 3, 7);
final int turnsReq; final int turnsReq;
final float baseDmgBonus, missingHPBonus; final float baseDmgBonus, KOThreshold;
final int damageRolls, blinkDistance; final int damageRolls, blinkDistance;
AttackLevel( int turns, float base, float missing, int rolls, int dist){ AttackLevel( int turns, float base, float threshold, int rolls, int dist){
turnsReq = turns; turnsReq = turns;
baseDmgBonus = base; missingHPBonus = missing; baseDmgBonus = base; KOThreshold = threshold;
damageRolls =rolls; blinkDistance = dist; damageRolls = rolls; blinkDistance = dist;
} }
public boolean canInstakill(Char defender){ public boolean canKO(Char defender){
return this == LVL_5 if (defender.properties().contains(Char.Property.MINIBOSS)
&& !defender.properties().contains(Char.Property.MINIBOSS) || defender.properties().contains(Char.Property.BOSS)){
&& !defender.properties().contains(Char.Property.BOSS); return (defender.HP/(float)defender.HT) <= (KOThreshold/5f);
} else {
return (defender.HP/(float)defender.HT) <= KOThreshold;
}
} }
public int damageRoll( Char attacker, Char defender){ public int damageRoll( Char attacker ){
int dmg = attacker.damageRoll(); int dmg = attacker.damageRoll();
for( int i = 1; i < damageRolls; i++){ for( int i = 1; i < damageRolls; i++){
int newDmg = attacker.damageRoll(); int newDmg = attacker.damageRoll();
if (newDmg > dmg) dmg = newDmg; if (newDmg > dmg) dmg = newDmg;
} }
float defenderHPPercent = 1f - (defender.HP / (float)defender.HT); return Math.round(dmg * (1f + baseDmgBonus));
return Math.round(dmg * (1f + baseDmgBonus + (missingHPBonus * defenderHPPercent)));
} }
public static AttackLevel getLvl(int turnsInvis){ public static AttackLevel getLvl(int turnsInvis){
@@ -120,16 +121,12 @@ public class Preparation extends Buff implements ActionIndicator.Action {
ActionIndicator.clearAction(this); ActionIndicator.clearAction(this);
} }
public int damageRoll(Char attacker, Char defender ){ public int damageRoll( Char attacker ){
AttackLevel lvl = AttackLevel.getLvl(turnsInvis); return AttackLevel.getLvl(turnsInvis).damageRoll(attacker);
if (lvl.canInstakill(defender)){ }
int dmg = lvl.damageRoll(attacker, defender);
defender.damage( Math.max(defender.HT, dmg), attacker ); public boolean canKO( Char defender ){
//even though the defender is dead, other effects should still proc (enchants, etc.) return AttackLevel.getLvl(turnsInvis).canKO(defender);
return Math.max( defender.HT, dmg);
} else {
return lvl.damageRoll(attacker, defender);
}
} }
@Override @Override
@@ -141,18 +138,15 @@ public class Preparation extends Buff implements ActionIndicator.Action {
public void tintIcon(Image icon) { public void tintIcon(Image icon) {
switch (AttackLevel.getLvl(turnsInvis)){ switch (AttackLevel.getLvl(turnsInvis)){
case LVL_1: case LVL_1:
icon.hardlight(1f, 1f, 1f);
break;
case LVL_2:
icon.hardlight(0f, 1f, 0f); icon.hardlight(0f, 1f, 0f);
break; break;
case LVL_3: case LVL_2:
icon.hardlight(1f, 1f, 0f); icon.hardlight(1f, 1f, 0f);
break; break;
case LVL_4: case LVL_3:
icon.hardlight(1f, 0.6f, 0f); icon.hardlight(1f, 0.6f, 0f);
break; break;
case LVL_5: case LVL_4:
icon.hardlight(1f, 0f, 0f); icon.hardlight(1f, 0f, 0f);
break; break;
} }
@@ -160,7 +154,7 @@ public class Preparation extends Buff implements ActionIndicator.Action {
@Override @Override
public float iconFadePercent() { public float iconFadePercent() {
if (AttackLevel.getLvl(turnsInvis) == AttackLevel.LVL_5){ if (AttackLevel.getLvl(turnsInvis) == AttackLevel.LVL_4){
return 0; return 0;
} else { } else {
float turnsForCur = AttackLevel.getLvl(turnsInvis).turnsReq; float turnsForCur = AttackLevel.getLvl(turnsInvis).turnsReq;
@@ -181,18 +175,11 @@ public class Preparation extends Buff implements ActionIndicator.Action {
String desc = Messages.get(this, "desc"); String desc = Messages.get(this, "desc");
AttackLevel lvl = AttackLevel.getLvl(turnsInvis); AttackLevel lvl = AttackLevel.getLvl(turnsInvis);
if (lvl.canInstakill(new Rat())){ desc += "\n\n" + Messages.get(this, "desc_dmg",
desc += "\n\n" + Messages.get(this, "desc_dmg_instakill", (int)(lvl.baseDmgBonus*100),
(int)(lvl.baseDmgBonus*100), (int)(lvl.KOThreshold*100),
(int)(lvl.baseDmgBonus*100 + lvl.missingHPBonus*100)); (int)(lvl.KOThreshold*20));
} else if (lvl.missingHPBonus > 0){
desc += "\n\n" + Messages.get(this, "desc_dmg_scale",
(int)(lvl.baseDmgBonus*100),
(int)(lvl.baseDmgBonus*100 + lvl.missingHPBonus*100));
} else {
desc += "\n\n" + Messages.get(this, "desc_dmg", (int)(lvl.baseDmgBonus*100));
}
if (lvl.damageRolls > 1){ if (lvl.damageRolls > 1){
desc += " " + Messages.get(this, "desc_dmg_likely"); desc += " " + Messages.get(this, "desc_dmg_likely");
@@ -41,7 +41,7 @@ public class SnipersMark extends FlavourBuff implements ActionIndicator.Action {
private static final String OBJECT = "object"; private static final String OBJECT = "object";
public static final float DURATION = 2f; public static final float DURATION = 4f;
{ {
type = buffType.POSITIVE; type = buffType.POSITIVE;
@@ -166,10 +166,10 @@ public class SpiritBow extends Weapon {
damage = Math.round(damage * 0.5f); damage = Math.round(damage * 0.5f);
break; break;
case DAMAGE: case DAMAGE:
//as distance increases so does damage, capping at 2.5x: //as distance increases so does damage, capping at 3x:
//1.20x|1.32x|1.45x|1.59x|1.76x|1.93x|2.13x|2.34x|2.50x //1.20x|1.35x|1.52x|1.71x|1.92x|2.16x|2.43x|2.74x|3.00x
int distance = Dungeon.level.distance(owner.pos, targetPos) - 1; int distance = Dungeon.level.distance(owner.pos, targetPos) - 1;
float multiplier = Math.min(2.5f, 1.2f * (float)Math.pow(1.1f, distance)); float multiplier = Math.min(2.5f, 1.2f * (float)Math.pow(1.125f, distance));
damage = Math.round(damage * multiplier); damage = Math.round(damage * multiplier);
break; break;
} }