v0.9.2: finished reworking combo

This commit is contained in:
Evan Debenham 2021-02-13 14:34:50 -05:00
parent 70e080ca6f
commit c1dd816a36
4 changed files with 218 additions and 161 deletions

View File

@ -122,12 +122,12 @@ actors.buffs.combo.name=Combo
actors.buffs.combo.combo=%d hit combo!
actors.buffs.combo.bad_target=You must target an enemy in attack range.
actors.buffs.combo.prompt=Select a target to attack.
actors.buffs.combo.desc=The gladiator builds momentum as they land successful blows. Each attack increases the combo counter by one, but taking too long between hits will reset the combo counter to 0.\n\nBuilding combo unlocks special combo attacks that cannot miss! A different attack is unlocked at 2, 4, 6, 8, and 10 combo count.
actors.buffs.combo$combomove.clobber_desc=_Clobber_ knocks an enemy back 2 tiles, but deals no damage and cannot knock into pits. Requires 2 combo, increments combo by 1, 1 use per combo.
actors.buffs.combo$combomove.slam_desc=_Slam_ deals 20% of your armor's blocking power as bonus damage for each combo you have. Requires 4 combo, resets combo when used.
actors.buffs.combo$combomove.parry_desc=_Parry_ ... . Requires 6 combo, resets combo if nothing is parried, 1 use per combo.
actors.buffs.combo$combomove.crush_desc=_???_ ... . Requires 8 combo, resets combo when used.
actors.buffs.combo$combomove.fury_desc=_???_ ... . Requires 10 combo, resets combo when used.
actors.buffs.combo.desc=The gladiator builds momentum as they land successful blows. Each attack increases the combo counter by one, but taking too long between hits will reset the combo counter to 0.\n\nBuilding combo unlocks special combo attacks that cannot miss! A different attack is unlocked at 2, 4, 6, 8, and 10 combo count. Some moves reset combo and some do not, but each move can only be used once per combo session.
actors.buffs.combo$combomove.clobber_desc=_2 Combo: Clobber_ knocks an enemy back 2 tiles, but deals no damage and cannot knock into pits. Increments combo by 1.
actors.buffs.combo$combomove.slam_desc=_4 Combo: Slam_ deals combo*20% of your armor's blocking power as bonus damage. Resets combo when used.
actors.buffs.combo$combomove.parry_desc=_6 Combo: Parry_ blocks the next attack within 1 turn when activated, and instantly retaliates to it. Resets combo if nothing is parried.
actors.buffs.combo$combomove.crush_desc=_8 Combo: Crush_ deals combo*25% of your melee damage to the primary target, and half that damage to all other enemies in a 7x7 AOE. Resets combo when used.
actors.buffs.combo$combomove.fury_desc=_10 Combo: Fury_ hits an enemy once for each combo you have, each hit deals 60% damage and can trigger enchantments. Resets combo when used.
actors.buffs.corruption.name=Corrupted
actors.buffs.corruption.desc=Corruption seeps into the essence of a being, twisting them against their former nature.\n\nCorrupted creatures will attack their allies, and ignore their former enemies. Corruption is damaging as well, and will slowly cause its target to succumb.\n\nCorruption is permanent, its effects only end in death.

View File

@ -47,6 +47,7 @@ import com.watabou.noosa.Image;
import com.watabou.noosa.audio.Sample;
import com.watabou.utils.Bundle;
import com.watabou.utils.Callback;
import com.watabou.utils.PathFinder;
import com.watabou.utils.Random;
public class Combo extends Buff implements ActionIndicator.Action {
@ -167,15 +168,14 @@ public class Combo extends Buff implements ActionIndicator.Action {
@Override
public void doAction() {
GameScene.show(new WndCombo(this));
//GameScene.selectCell(finisher);
}
public enum ComboMove {
CLOBBER(2, 0xFF00FF00),
SLAM (4, 0xFFCCFF00),
PARRY (6, 0xFFFFFF00), //TODO implement
CRUSH (8, 0xFFFFCC00), //TODO rework
FURY (10, 0xFFFF0000); //TODO rework
PARRY (6, 0xFFFFFF00),
CRUSH (8, 0xFFFFCC00),
FURY (10, 0xFFFF0000); //TODO can currently input other actions while attacking with fury
public int comboReq, tintColor;
@ -211,16 +211,201 @@ public class Combo extends Buff implements ActionIndicator.Action {
public void useMove(ComboMove move){
if (move == ComboMove.PARRY){
//TODO
parryUsed = true;
Buff.affect(target, ParryTracker.class, Actor.TICK);
((Hero)target).spendAndNext(Actor.TICK);
} else {
moveBeingUsed = move;
GameScene.selectCell(listener);
}
}
public static class ParryTracker extends FlavourBuff{
{ actPriority = HERO_PRIO+1;}
public boolean parried;
@Override
public void detach() {
if (!parried) target.buff(Combo.class).detach();
super.detach();
}
}
public static class RiposteTracker extends Buff{
{ actPriority = VFX_PRIO;}
public Char enemy;
@Override
public boolean act() {
moveBeingUsed = ComboMove.PARRY;
target.sprite.attack(enemy.pos, new Callback() {
@Override
public void call() {
target.buff(Combo.class).doAttack(enemy);
}
});
detach();
return true;
}
}
private static ComboMove moveBeingUsed;
private void doAttack(final Char enemy){
AttackIndicator.target(enemy);
boolean wasAlly = enemy.alignment == target.alignment;
Hero hero = (Hero)target;
if (enemy.defenseSkill(target) >= Char.INFINITE_EVASION){
enemy.sprite.showStatus( CharSprite.NEUTRAL, enemy.defenseVerb() );
Sample.INSTANCE.play(Assets.Sounds.MISS);
} else if (enemy.isInvulnerable(target.getClass())){
enemy.sprite.showStatus( CharSprite.POSITIVE, Messages.get(Char.class, "invulnerable") );
Sample.INSTANCE.play(Assets.Sounds.MISS);
} else {
int dmg = target.damageRoll();
//variance in damage dealt
switch (moveBeingUsed) {
case CLOBBER:
dmg = 0;
break;
case SLAM:
dmg += Math.round(target.drRoll() * count/5f);
break;
case CRUSH:
dmg = Math.round(dmg * 0.25f*count);
break;
case FURY:
dmg = Math.round(dmg * 0.6f);
break;
}
dmg = enemy.defenseProc(target, dmg);
dmg -= enemy.drRoll();
if (enemy.buff(Vulnerable.class) != null) {
dmg *= 1.33f;
}
dmg = target.attackProc(enemy, dmg);
enemy.damage(dmg, target);
//special effects
switch (moveBeingUsed) {
case CLOBBER:
hit( enemy );
if (enemy.isAlive()) {
//trace a ballistica to our target (which will also extend past them
Ballistica trajectory = new Ballistica(target.pos, enemy.pos, Ballistica.STOP_TARGET);
//trim it to just be the part that goes past them
trajectory = new Ballistica(trajectory.collisionPos, trajectory.path.get(trajectory.path.size() - 1), Ballistica.PROJECTILE);
//knock them back along that ballistica, ensuring they don't fall into a pit
int dist = 2;
while (dist > 0 && Dungeon.level.pit[trajectory.path.get(dist)]){
dist--;
}
WandOfBlastWave.throwChar(enemy, trajectory, dist, true, false);
}
break;
case PARRY:
hit( enemy );
break;
case CRUSH:
WandOfBlastWave.BlastWave.blast(enemy.pos);
PathFinder.buildDistanceMap(target.pos, Dungeon.level.passable, 3);
for (Char ch : Actor.chars()){
if (ch != enemy && ch.alignment == Char.Alignment.ENEMY
&& PathFinder.distance[ch.pos] < Integer.MAX_VALUE){
int aoeHit = Math.round(target.damageRoll() * 0.25f*count);
aoeHit /= 2;
aoeHit -= ch.drRoll();
if (ch.buff(Vulnerable.class) != null) aoeHit *= 1.33f;
ch.damage(aoeHit, target);
ch.sprite.bloodBurstA(target.sprite.center(), dmg);
ch.sprite.flash();
if (!ch.isAlive()) {
if (hero.hasTalent(Talent.LETHAL_DEFENSE) && hero.buff(BrokenSeal.WarriorShield.class) != null){
BrokenSeal.WarriorShield shield = hero.buff(BrokenSeal.WarriorShield.class);
shield.supercharge(Math.round(shield.maxShield() * hero.pointsInTalent(Talent.LETHAL_DEFENSE)/3f));
}
}
}
}
break;
default:
//nothing
break;
}
if (target.buff(FireImbue.class) != null) target.buff(FireImbue.class).proc(enemy);
if (target.buff(FrostImbue.class) != null) target.buff(FrostImbue.class).proc(enemy);
target.hitSound(Random.Float(0.87f, 1.15f));
if (moveBeingUsed != ComboMove.FURY) Sample.INSTANCE.play(Assets.Sounds.HIT_STRONG);
enemy.sprite.bloodBurstA(target.sprite.center(), dmg);
enemy.sprite.flash();
if (!enemy.isAlive()) {
GLog.i(Messages.capitalize(Messages.get(Char.class, "defeat", enemy.name())));
}
}
//Post-attack behaviour
switch(moveBeingUsed){
case CLOBBER:
clobberUsed = true;
if (getHighestMove() == null) ActionIndicator.clearAction(Combo.this);
hero.spendAndNext(hero.attackDelay());
break;
case PARRY:
hero.next();
break;
case FURY:
count--;
//fury attacks as many times as you have combo count
if (count > 0 && enemy.isAlive() && (wasAlly || enemy.alignment != target.alignment)){
target.sprite.attack(enemy.pos, new Callback() {
@Override
public void call() {
doAttack(enemy);
}
});
} else {
detach();
Sample.INSTANCE.play(Assets.Sounds.HIT_STRONG);
ActionIndicator.clearAction(Combo.this);
hero.spendAndNext(hero.attackDelay());
}
break;
default:
detach();
ActionIndicator.clearAction(Combo.this);
hero.spendAndNext(hero.attackDelay());
break;
}
if (!enemy.isAlive() || (!wasAlly && enemy.alignment == target.alignment)) {
if (hero.hasTalent(Talent.LETHAL_DEFENSE) && hero.buff(BrokenSeal.WarriorShield.class) != null){
BrokenSeal.WarriorShield shield = hero.buff(BrokenSeal.WarriorShield.class);
shield.supercharge(Math.round(shield.maxShield() * hero.pointsInTalent(Talent.LETHAL_DEFENSE)/3f));
}
}
}
private CellSelector.Listener listener = new CellSelector.Listener() {
@Override
@ -242,154 +427,6 @@ public class Combo extends Buff implements ActionIndicator.Action {
}
}
private void doAttack(final Char enemy){
AttackIndicator.target(enemy);
boolean wasAlly = enemy.alignment == target.alignment;
if (enemy.defenseSkill(target) >= Char.INFINITE_EVASION){
enemy.sprite.showStatus( CharSprite.NEUTRAL, enemy.defenseVerb() );
Sample.INSTANCE.play(Assets.Sounds.MISS);
} else if (enemy.isInvulnerable(target.getClass())){
enemy.sprite.showStatus( CharSprite.POSITIVE, Messages.get(Char.class, "invulnerable") );
Sample.INSTANCE.play(Assets.Sounds.MISS);
} else {
int dmg = target.damageRoll();
//variance in damage dealt
switch (moveBeingUsed) {
case CLOBBER:
dmg = 0;
break;
case SLAM:
dmg += Math.round(target.drRoll() * count/5f);
break;
case CRUSH:
//rolls 4 times, takes the highest roll
for (int i = 1; i < 4; i++) {
int dmgReroll = target.damageRoll();
if (dmgReroll > dmg) dmg = dmgReroll;
}
dmg = Math.round(dmg * 2.5f);
break;
case FURY:
dmg = Math.round(dmg * 0.6f);
break;
}
dmg = enemy.defenseProc(target, dmg);
dmg -= enemy.drRoll();
if (enemy.buff(Vulnerable.class) != null) {
dmg *= 1.33f;
}
dmg = target.attackProc(enemy, dmg);
enemy.damage(dmg, target);
//special effects
switch (moveBeingUsed) {
case CLOBBER:
if (enemy.isAlive()) {
//trace a ballistica to our target (which will also extend past them
Ballistica trajectory = new Ballistica(target.pos, enemy.pos, Ballistica.STOP_TARGET);
//trim it to just be the part that goes past them
trajectory = new Ballistica(trajectory.collisionPos, trajectory.path.get(trajectory.path.size() - 1), Ballistica.PROJECTILE);
//knock them back along that ballistica, ensuring they don't fall into a pit
int dist = 2;
while (dist > 0 && Dungeon.level.pit[trajectory.path.get(dist)]){
dist--;
}
WandOfBlastWave.throwChar(enemy, trajectory, dist, true, false);
hit( enemy );
}
break;
case SLAM:
BrokenSeal.WarriorShield shield = Buff.affect(target, BrokenSeal.WarriorShield.class);
if (shield != null) {
shield.supercharge(dmg / 2);
}
break;
default:
//nothing
break;
}
if (target.buff(FireImbue.class) != null) target.buff(FireImbue.class).proc(enemy);
if (target.buff(FrostImbue.class) != null) target.buff(FrostImbue.class).proc(enemy);
target.hitSound(Random.Float(0.87f, 1.15f));
if (moveBeingUsed != ComboMove.FURY) Sample.INSTANCE.play(Assets.Sounds.HIT_STRONG);
enemy.sprite.bloodBurstA(target.sprite.center(), dmg);
enemy.sprite.flash();
if (!enemy.isAlive()) {
GLog.i(Messages.capitalize(Messages.get(Char.class, "defeat", enemy.name())));
}
}
Hero hero = (Hero)target;
//Post-attack behaviour
switch(moveBeingUsed){
case CLOBBER:
clobberUsed = true;
if (getHighestMove() == null) ActionIndicator.clearAction(Combo.this);
hero.spendAndNext(hero.attackDelay());
break;
/*case CLEAVE:
//combo isn't reset, but rather increments with a cleave kill, and grants more time.
//this includes corrupting kills (which is why we check alignment
if (!enemy.isAlive() || (!wasAlly && enemy.alignment == target.alignment)) {
hit( enemy );
comboTime = 12f;
} else {
detach();
ActionIndicator.clearAction(Combo.this);
}
hero.spendAndNext(hero.attackDelay());
break;*/
case FURY:
count--;
//fury attacks as many times as you have combo count
if (count > 0 && enemy.isAlive() && (wasAlly || enemy.alignment != target.alignment)){
target.sprite.attack(enemy.pos, new Callback() {
@Override
public void call() {
doAttack(enemy);
}
});
} else {
detach();
Sample.INSTANCE.play(Assets.Sounds.HIT_STRONG);
ActionIndicator.clearAction(Combo.this);
hero.spendAndNext(hero.attackDelay());
}
break;
default:
detach();
ActionIndicator.clearAction(Combo.this);
hero.spendAndNext(hero.attackDelay());
break;
}
if (!enemy.isAlive() || (!wasAlly && enemy.alignment == target.alignment)) {
if (hero.hasTalent(Talent.LETHAL_DEFENSE) && hero.buff(BrokenSeal.WarriorShield.class) != null){
BrokenSeal.WarriorShield shield = hero.buff(BrokenSeal.WarriorShield.class);
shield.supercharge(Math.round(shield.maxShield() * hero.pointsInTalent(Talent.LETHAL_DEFENSE)/3f));
}
}
}
@Override
public String prompt() {
return Messages.get(Combo.class, "prompt");

View File

@ -52,6 +52,7 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Regeneration;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.SnipersMark;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Vertigo;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mob;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Monk;
import com.shatteredpixel.shatteredpixeldungeon.effects.CellEmitter;
import com.shatteredpixel.shatteredpixeldungeon.effects.CheckedCell;
import com.shatteredpixel.shatteredpixeldungeon.effects.Flare;
@ -409,6 +410,13 @@ public class Hero extends Char {
@Override
public int defenseSkill( Char enemy ) {
if (buff(Combo.ParryTracker.class) != null){
if (canAttack(enemy)){
Buff.affect(this, Combo.RiposteTracker.class).enemy = enemy;
}
return INFINITE_EVASION;
}
float evasion = defenseSkill;
@ -424,7 +432,19 @@ public class Hero extends Char {
return Math.round(evasion);
}
@Override
public String defenseVerb() {
Combo.ParryTracker parry = buff(Combo.ParryTracker.class);
if (parry == null){
return super.defenseVerb();
} else {
parry.parried = true;
parry.detach();
return Messages.get(Monk.class, "parried");
}
}
@Override
public int drRoll() {
int dr = 0;

View File

@ -36,7 +36,7 @@ import com.watabou.noosa.Image;
public class WndCombo extends Window {
private static final int WIDTH_P = 120;
private static final int WIDTH_L = 144;
private static final int WIDTH_L = 160;
private static final int MARGIN = 2;