327 lines
8.9 KiB
Java
327 lines
8.9 KiB
Java
/*
|
|
* Pixel Dungeon
|
|
* Copyright (C) 2012-2015 Oleg Dolya
|
|
*
|
|
* Shattered Pixel Dungeon
|
|
* Copyright (C) 2014-2015 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.buffs;
|
|
|
|
import com.shatteredpixel.shatteredpixeldungeon.Assets;
|
|
import com.shatteredpixel.shatteredpixeldungeon.Badges;
|
|
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.mobs.Mob;
|
|
import com.shatteredpixel.shatteredpixeldungeon.effects.Pushing;
|
|
import com.shatteredpixel.shatteredpixeldungeon.items.Item;
|
|
import com.shatteredpixel.shatteredpixeldungeon.levels.Level;
|
|
import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
|
|
import com.shatteredpixel.shatteredpixeldungeon.scenes.CellSelector;
|
|
import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene;
|
|
import com.shatteredpixel.shatteredpixeldungeon.sprites.ItemSprite;
|
|
import com.shatteredpixel.shatteredpixeldungeon.sprites.ItemSpriteSheet;
|
|
import com.shatteredpixel.shatteredpixeldungeon.ui.ActionIndicator;
|
|
import com.shatteredpixel.shatteredpixeldungeon.ui.AttackIndicator;
|
|
import com.shatteredpixel.shatteredpixeldungeon.ui.BuffIndicator;
|
|
import com.shatteredpixel.shatteredpixeldungeon.utils.GLog;
|
|
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.Random;
|
|
|
|
public class Combo extends Buff implements ActionIndicator.Action {
|
|
|
|
private int count = 0;
|
|
private float comboTime = 0f;
|
|
private int misses = 0;
|
|
|
|
@Override
|
|
public int icon() {
|
|
return BuffIndicator.COMBO;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return Messages.get(this, "name");
|
|
}
|
|
|
|
public void hit() {
|
|
|
|
count++;
|
|
comboTime = 4f;
|
|
misses = 0;
|
|
|
|
if (count >= 2) {
|
|
|
|
ActionIndicator.setAction( this );
|
|
Badges.validateMasteryCombo( count );
|
|
|
|
GLog.p( Messages.get(this, "combo", count) );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
public void miss(){
|
|
misses++;
|
|
comboTime = 4f;
|
|
if (misses >= 2){
|
|
detach();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void detach() {
|
|
super.detach();
|
|
ActionIndicator.clearAction(this);
|
|
}
|
|
|
|
@Override
|
|
public boolean act() {
|
|
comboTime-=TICK;
|
|
spend(TICK);
|
|
if (comboTime <= 0) {
|
|
detach();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public String desc() {
|
|
String desc = Messages.get(this, "desc");
|
|
|
|
if (count >= 10) desc += "\n\n" + Messages.get(this, "fury_desc");
|
|
else if (count >= 8)desc += "\n\n" + Messages.get(this, "crush_desc");
|
|
else if (count >= 6)desc += "\n\n" + Messages.get(this, "slam_desc");
|
|
else if (count >= 4)desc += "\n\n" + Messages.get(this, "cleave_desc");
|
|
else if (count >= 2)desc += "\n\n" + Messages.get(this, "clobber_desc");
|
|
|
|
return desc;
|
|
}
|
|
|
|
private static final String COUNT = "count";
|
|
private static final String TIME = "combotime";
|
|
private static final String MISSES= "misses";
|
|
|
|
@Override
|
|
public void storeInBundle(Bundle bundle) {
|
|
super.storeInBundle(bundle);
|
|
bundle.put(COUNT, count);
|
|
bundle.put(TIME, comboTime);
|
|
bundle.put(MISSES, misses);
|
|
}
|
|
|
|
@Override
|
|
public void restoreFromBundle(Bundle bundle) {
|
|
super.restoreFromBundle(bundle);
|
|
count = bundle.getInt( COUNT );
|
|
if (count >= 2) ActionIndicator.setAction(this);
|
|
comboTime = bundle.getFloat( TIME );
|
|
misses = bundle.getInt( MISSES );
|
|
}
|
|
|
|
@Override
|
|
public Image getIcon() {
|
|
Image icon;
|
|
if (((Hero)target).belongings.weapon != null){
|
|
icon = new ItemSprite(Dungeon.hero.belongings.weapon);
|
|
} else {
|
|
icon = new ItemSprite(new Item(){ {image = ItemSpriteSheet.WEAPON; }});
|
|
}
|
|
|
|
if (count >= 10) icon.tint(0xFFFF0000);
|
|
else if (count >= 8)icon.tint(0xFFFFCC00);
|
|
else if (count >= 6)icon.tint(0xFFFFFF00);
|
|
else if (count >= 4)icon.tint(0xFFCCFF00);
|
|
else icon.tint(0xFF00FF00);
|
|
|
|
return icon;
|
|
}
|
|
|
|
@Override
|
|
public void doAction() {
|
|
GameScene.selectCell(finisher);
|
|
}
|
|
|
|
private enum finisherType{
|
|
CLOBBER, CLEAVE, SLAM, CRUSH, FURY;
|
|
}
|
|
|
|
private CellSelector.Listener finisher = new CellSelector.Listener() {
|
|
|
|
private finisherType type;
|
|
|
|
@Override
|
|
public void onSelect(Integer cell) {
|
|
if (cell == null) return;
|
|
final Char enemy = Actor.findChar( cell );
|
|
if (enemy == null || !((Hero)target).canAttack(enemy) || target.isCharmedBy( enemy )){
|
|
GLog.w( Messages.get(Combo.class, "bad_target") );
|
|
} else {
|
|
target.sprite.attack(cell, new Callback() {
|
|
@Override
|
|
public void call() {
|
|
if (count >= 10) type = finisherType.FURY;
|
|
else if (count >= 8)type = finisherType.CRUSH;
|
|
else if (count >= 6)type = finisherType.SLAM;
|
|
else if (count >= 4)type = finisherType.CLEAVE;
|
|
else type = finisherType.CLOBBER;
|
|
doAttack(enemy);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
private void doAttack(final Char enemy){
|
|
|
|
AttackIndicator.target(enemy);
|
|
|
|
int dmg = target.damageRoll();
|
|
|
|
//variance in damage dealt
|
|
switch(type){
|
|
case CLOBBER:
|
|
dmg = Math.round(dmg*0.5f);
|
|
break;
|
|
case CLEAVE:
|
|
dmg = Math.round(dmg*1.4f);
|
|
break;
|
|
case SLAM:
|
|
dmg = Math.round(dmg*1.6f);
|
|
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.4f);
|
|
break;
|
|
}
|
|
|
|
dmg -= Random.IntRange( 0, enemy.dr() );
|
|
dmg = target.attackProc(enemy, dmg);
|
|
dmg = enemy.defenseProc(target, dmg);
|
|
enemy.damage( dmg, this );
|
|
|
|
//special effects
|
|
switch (type){
|
|
case CLOBBER:
|
|
if (enemy.isAlive()){
|
|
if (!enemy.properties().contains(Char.Property.IMMOVABLE)){
|
|
for (int i=0; i < Level.NEIGHBOURS8.length; i++) {
|
|
int ofs = Level.NEIGHBOURS8[i];
|
|
if (enemy.pos - target.pos == ofs) {
|
|
int newPos = enemy.pos + ofs;
|
|
if ((Level.passable[newPos] || Level.avoid[newPos]) && Actor.findChar( newPos ) == null) {
|
|
|
|
Actor.addDelayed( new Pushing( enemy, enemy.pos, newPos ), -1 );
|
|
|
|
enemy.pos = newPos;
|
|
// FIXME
|
|
if (enemy instanceof Mob) {
|
|
Dungeon.level.mobPress( (Mob)enemy );
|
|
} else {
|
|
Dungeon.level.press( newPos, enemy );
|
|
}
|
|
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
Buff.prolong(enemy, Paralysis.class, Random.NormalIntRange(0, 2));
|
|
}
|
|
break;
|
|
case SLAM:
|
|
target.SHLD += dmg/2;
|
|
break;
|
|
default:
|
|
//nothing
|
|
break;
|
|
}
|
|
|
|
if (target.buff(FireImbue.class) != null)
|
|
target.buff(FireImbue.class).proc(enemy);
|
|
if (target.buff(EarthImbue.class) != null)
|
|
target.buff(EarthImbue.class).proc(enemy);
|
|
|
|
Sample.INSTANCE.play( Assets.SND_HIT, 1, 1, Random.Float( 0.8f, 1.25f ) );
|
|
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(type){
|
|
case CLEAVE:
|
|
if (!enemy.isAlive()) {
|
|
//combo isn't reset, but rather increments with a cleave kill
|
|
hit();
|
|
} 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()){
|
|
target.sprite.attack(enemy.pos, new Callback() {
|
|
@Override
|
|
public void call() {
|
|
doAttack(enemy);
|
|
}
|
|
});
|
|
} else {
|
|
detach();
|
|
ActionIndicator.clearAction(Combo.this);
|
|
hero.spendAndNext(hero.attackDelay());
|
|
}
|
|
break;
|
|
|
|
default:
|
|
detach();
|
|
ActionIndicator.clearAction(Combo.this);
|
|
hero.spendAndNext(hero.attackDelay());
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
@Override
|
|
public String prompt() {
|
|
if (count >= 10) return Messages.get(Combo.class, "fury_prompt");
|
|
else if (count >= 8)return Messages.get(Combo.class, "crush_prompt");
|
|
else if (count >= 6)return Messages.get(Combo.class, "slam_prompt");
|
|
else if (count >= 4)return Messages.get(Combo.class, "cleave_prompt");
|
|
else return Messages.get(Combo.class, "clobber_prompt");
|
|
}
|
|
};
|
|
}
|