v0.9.2: finished reworking combo
This commit is contained in:
parent
70e080ca6f
commit
c1dd816a36
|
@ -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.
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user