v0.6.2: reworked the assassin subclass

This commit is contained in:
Evan Debenham 2017-09-11 20:59:19 -04:00
parent 6cda24e6ed
commit 60ca4b0180
9 changed files with 332 additions and 7 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 337 B

After

Width:  |  Height:  |  Size: 337 B

View File

@ -35,6 +35,7 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Frost;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Hunger;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.MagicalSleep;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Paralysis;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Preparation;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Slow;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Speed;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Vertigo;
@ -133,7 +134,13 @@ public abstract class Char extends Actor {
int dr = this instanceof Hero && ((Hero)this).rangedWeapon != null && ((Hero)this).subClass ==
HeroSubClass.SNIPER ? 0 : enemy.drRoll();
int dmg = damageRoll();
int dmg;
Preparation prep = buff(Preparation.class);
if (prep != null){
dmg = prep.damageRoll(this, enemy);
} else {
dmg = damageRoll();
}
int effectiveDamage = Math.max( dmg - dr, 0 );
effectiveDamage = attackProc( enemy, effectiveDamage );

View File

@ -23,6 +23,8 @@ package com.shatteredpixel.shatteredpixeldungeon.actors.buffs;
import com.shatteredpixel.shatteredpixeldungeon.Dungeon;
import com.shatteredpixel.shatteredpixeldungeon.actors.Char;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.HeroSubClass;
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.CloakOfShadows;
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.TimekeepersHourglass;
import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
@ -41,6 +43,9 @@ public class Invisibility extends FlavourBuff {
public boolean attachTo( Char target ) {
if (super.attachTo( target )) {
target.invisible++;
if (target instanceof Hero && ((Hero) target).subClass == HeroSubClass.ASSASSIN){
Buff.affect(target, Preparation.class);
}
return true;
} else {
return false;

View File

@ -0,0 +1,294 @@
/*
* Pixel Dungeon
* Copyright (C) 2012-2015 Oleg Dolya
*
* Shattered Pixel Dungeon
* Copyright (C) 2014-2017 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.Dungeon;
import com.shatteredpixel.shatteredpixeldungeon.actors.Actor;
import com.shatteredpixel.shatteredpixeldungeon.actors.Char;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Rat;
import com.shatteredpixel.shatteredpixeldungeon.effects.CellEmitter;
import com.shatteredpixel.shatteredpixeldungeon.effects.Effects;
import com.shatteredpixel.shatteredpixeldungeon.effects.Speck;
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.ui.ActionIndicator;
import com.shatteredpixel.shatteredpixeldungeon.ui.BuffIndicator;
import com.shatteredpixel.shatteredpixeldungeon.utils.BArray;
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.PathFinder;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class Preparation extends Buff implements ActionIndicator.Action {
{
//always acts after other buffs, so invisibility effects can process first
actPriority = 4;
}
public enum AttackLevel{
LVL_1( 1, 0.1f, 0.0f, 1, 0),
LVL_2( 3, 0.2f, 0.0f, 1, 1),
LVL_3( 6, 0.3f, 0.0f, 2, 3),
LVL_4( 11, 0.4f, 0.4f, 2, 5),
LVL_5( 16, 0.6f, 0.6f, 1, 7);
final int turnsReq;
final float baseDmgBonus, missingHPBonus;
final int damageRolls, blinkDistance;
AttackLevel( int turns, float base, float missing, int rolls, int dist){
turnsReq = turns;
baseDmgBonus = base; missingHPBonus = missing;
damageRolls =rolls; blinkDistance = dist;
}
public boolean canInstakill(Char defender){
return this == LVL_5
&& !defender.properties().contains(Char.Property.MINIBOSS)
&& !defender.properties().contains(Char.Property.MINIBOSS);
}
public int damageRoll( Char attacker, Char defender){
int dmg = attacker.damageRoll();
for( int i = 1; i < damageRolls; i++){
int newDmg = attacker.damageRoll();
if (newDmg > dmg) dmg = newDmg;
}
float defenderHPPercent = defender.HP / (float)defender.HT;
return Math.round(dmg * (1f + baseDmgBonus + (missingHPBonus * defenderHPPercent)));
}
public static AttackLevel getLvl(int turnsInvis){
List<AttackLevel> values = Arrays.asList(values());
Collections.reverse(values);
for ( AttackLevel lvl : values ){
if (turnsInvis >= lvl.turnsReq){
return lvl;
}
}
return LVL_1;
}
}
private int turnsInvis = 0;
@Override
public boolean act() {
if (target.invisible > 0){
turnsInvis++;
if (AttackLevel.getLvl(turnsInvis).blinkDistance > 0 && target == Dungeon.hero){
ActionIndicator.setAction(this);
}
BuffIndicator.refreshHero();
spend(TICK);
} else {
detach();
}
return true;
}
@Override
public void detach() {
super.detach();
ActionIndicator.clearAction(this);
}
public int damageRoll(Char attacker, Char defender ){
AttackLevel lvl = AttackLevel.getLvl(turnsInvis);
if (lvl.canInstakill(defender)){
int dmg = lvl.damageRoll(attacker, defender);
defender.damage( Math.max(defender.HT, dmg), attacker );
//even though the defender is dead, other effects should still proc (enchants, etc.)
return Math.max( defender.HT, dmg);
} else {
return lvl.damageRoll(attacker, defender);
}
}
@Override
public int icon() {
return BuffIndicator.PREPARATION;
}
@Override
public void tintIcon(Image icon) {
switch (AttackLevel.getLvl(turnsInvis)){
case LVL_1:
icon.hardlight(1f, 1f, 1f);
break;
case LVL_2:
icon.hardlight(0f, 1f, 0f);
break;
case LVL_3:
icon.hardlight(1f, 1f, 0f);
break;
case LVL_4:
icon.hardlight(1f, 0.6f, 0f);
break;
case LVL_5:
icon.hardlight(1f, 0f, 0f);
break;
}
}
@Override
public String toString() {
return Messages.get(this, "name");
}
@Override
public String desc() {
String desc = Messages.get(this, "desc");
AttackLevel lvl = AttackLevel.getLvl(turnsInvis);
if (lvl.canInstakill(new Rat())){
desc += "\n\n" + Messages.get(this, "desc_dmg_instakill",
(int)(lvl.baseDmgBonus*100),
(int)(lvl.baseDmgBonus*100 + lvl.missingHPBonus*100));
} 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){
desc += " " + Messages.get(this, "desc_dmg_likely");
}
if (lvl.blinkDistance > 0){
desc += "\n\n" + Messages.get(this, "desc_blink", lvl.blinkDistance);
}
desc += "\n\n" + Messages.get(this, "desc_invis_time", turnsInvis);
if (lvl.ordinal() != AttackLevel.values().length-1){
AttackLevel next = AttackLevel.values()[lvl.ordinal()+1];
desc += "\n" + Messages.get(this, "desc_invis_next", next.turnsReq);
}
return desc;
}
private static final String TURNS = "turnsInvis";
@Override
public void restoreFromBundle(Bundle bundle) {
super.restoreFromBundle(bundle);
turnsInvis = bundle.getInt(TURNS);
if (AttackLevel.getLvl(turnsInvis).blinkDistance > 0){
ActionIndicator.setAction(this);
}
}
@Override
public void storeInBundle(Bundle bundle) {
super.storeInBundle(bundle);
bundle.put(TURNS, turnsInvis);
}
@Override
public Image getIcon() {
Image actionIco = Effects.get(Effects.Type.WOUND);
tintIcon(actionIco);
return actionIco;
}
@Override
public void doAction() {
GameScene.selectCell(attack);
}
private CellSelector.Listener attack = new CellSelector.Listener() {
@Override
public void onSelect(Integer cell) {
if (cell == null) return;
final Char enemy = Actor.findChar( cell );
if (enemy == null || Dungeon.hero.isCharmedBy(enemy)){
GLog.w(Messages.get(Preparation.class, "no_target"));
} else {
//just attack them then!
if (Dungeon.hero.canAttack(enemy)){
if (Dungeon.hero.handle( cell )) {
Dungeon.hero.next();
}
}
AttackLevel lvl = AttackLevel.getLvl(turnsInvis);
boolean[] passable = new boolean[Dungeon.level.length()];
PathFinder.buildDistanceMap(Dungeon.hero.pos, BArray.or(Level.passable, Level.avoid, passable), lvl.blinkDistance+1);
if (PathFinder.distance[cell] == Integer.MAX_VALUE){
GLog.w(Messages.get(Preparation.class, "out_of_reach"));
return;
}
//we can move through enemies when determining blink distance,
// but not when actually jumping to a location
for (Char ch : Actor.chars()){
if (ch != Dungeon.hero) passable[ch.pos] = false;
}
PathFinder.Path path = PathFinder.find(Dungeon.hero.pos, cell, passable);
int attackPos = path.get(path.size()-2);
if (Dungeon.level.distance(attackPos, Dungeon.hero.pos) > lvl.blinkDistance){
GLog.w(Messages.get(Preparation.class, "out_of_reach"));
return;
}
Dungeon.hero.pos = attackPos;
Dungeon.level.press(Dungeon.hero.pos, Dungeon.hero);
//prevents the hero from being interrupted by seeing new enemies
Dungeon.level.updateFieldOfView(Dungeon.hero, Level.fieldOfView);
Dungeon.hero.checkVisibleMobs();
Dungeon.hero.sprite.place( Dungeon.hero.pos );
Dungeon.hero.sprite.turnTo( Dungeon.hero.pos, cell);
CellEmitter.get( Dungeon.hero.pos ).burst( Speck.factory( Speck.WOOL ), 6 );
Sample.INSTANCE.play( Assets.SND_PUFF );
if (Dungeon.hero.handle( cell )) {
Dungeon.hero.next();
}
}
}
@Override
public String prompt() {
return Messages.get(Preparation.class, "prompt", AttackLevel.getLvl(turnsInvis).blinkDistance);
}
};
}

View File

@ -970,7 +970,7 @@ public class Hero extends Char {
super.damage( dmg, src );
}
private void checkVisibleMobs() {
public void checkVisibleMobs() {
ArrayList<Mob> visible = new ArrayList<>();
boolean newMob = false;

View File

@ -32,12 +32,12 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Amok;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Corruption;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Hunger;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Preparation;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Sleep;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.SoulMark;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Terror;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Weakness;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.HeroSubClass;
import com.shatteredpixel.shatteredpixeldungeon.effects.Flare;
import com.shatteredpixel.shatteredpixeldungeon.effects.Speck;
import com.shatteredpixel.shatteredpixeldungeon.effects.Surprise;
@ -475,8 +475,7 @@ public abstract class Mob extends Char {
@Override
public int defenseProc( Char enemy, int damage ) {
if (!enemySeen && enemy == Dungeon.hero && Dungeon.hero.canSurpriseAttack()) {
if (((Hero)enemy).subClass == HeroSubClass.ASSASSIN) {
damage *= 1.25f;
if (enemy.buff(Preparation.class) != null) {
Wound.hit(this);
} else {
Surprise.hit(this);

View File

@ -22,8 +22,8 @@
package com.shatteredpixel.shatteredpixeldungeon.effects;
import com.shatteredpixel.shatteredpixeldungeon.Dungeon;
import com.shatteredpixel.shatteredpixeldungeon.tiles.DungeonTilemap;
import com.shatteredpixel.shatteredpixeldungeon.actors.Char;
import com.shatteredpixel.shatteredpixeldungeon.tiles.DungeonTilemap;
import com.watabou.noosa.Game;
import com.watabou.noosa.Group;
import com.watabou.noosa.Image;
@ -37,6 +37,7 @@ public class Wound extends Image {
public Wound() {
super( Effects.get( Effects.Type.WOUND ) );
hardlight(1f, 0f, 0f);
origin.set( width / 2, height / 2 );
}

View File

@ -24,8 +24,11 @@ package com.shatteredpixel.shatteredpixeldungeon.items.artifacts;
import com.shatteredpixel.shatteredpixeldungeon.Assets;
import com.shatteredpixel.shatteredpixeldungeon.actors.Char;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.LockedFloor;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Preparation;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.HeroSubClass;
import com.shatteredpixel.shatteredpixeldungeon.items.Item;
import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
import com.shatteredpixel.shatteredpixeldungeon.sprites.CharSprite;
@ -208,6 +211,9 @@ public class CloakOfShadows extends Artifact {
public boolean attachTo( Char target ) {
if (super.attachTo( target )) {
target.invisible++;
if (target instanceof Hero && ((Hero) target).subClass == HeroSubClass.ASSASSIN){
Buff.affect(target, Preparation.class);
}
return true;
} else {
return false;

View File

@ -172,6 +172,19 @@ actors.buffs.poison.ondeath=You died from poison...
actors.buffs.poison.rankings_desc=Succumbed to Poison
actors.buffs.poison.desc=Poison works its way through the body, slowly impairing its internal functioning.\n\nPoison deals damage each turn proportional to how long until it expires.\n\nTurns of poison remaining: %s.
actors.buffs.preparation.name=Preparation
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_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 none-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_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_next=His attack will become stronger at _%d turns._
actors.buffs.preparation.prompt=Select a target to attack!\nMax blink distance: %d
actors.buffs.preparation.nothing_there=There's nothing to attack there.
actors.buffs.preparation.out_of_reach=That target is out of reach.
actors.buffs.recharging.name=Recharging
actors.buffs.recharging.desc=Energy is coursing through you, improving the rate that your wands and staffs charge.\n\nEach turn this buff will increase current charge by one quarter, in addition to regular recharge.\n\nTurns of recharging remaining: %s.
@ -262,7 +275,7 @@ actors.hero.herosubclass.warlock_desc=When using wands on an enemy, the _Warlock
actors.hero.herosubclass.battlemage=battlemage
actors.hero.herosubclass.battlemage_desc=When fighting with his staff, the _Battlemage_ conjures bonus effects depending on the wand his staff is imbued with. His staff will also gain charge through combat.
actors.hero.herosubclass.assassin=assassin
actors.hero.herosubclass.assassin_desc=When performing a surprise attack, the _Assassin_ inflicts additional damage to his target.
actors.hero.herosubclass.assassin_desc=While invisible the _Assassin_ prepares a deadly strike on his next attack. The longer spent invisible, the more powerful the attack will be.
actors.hero.herosubclass.freerunner=freerunner
actors.hero.herosubclass.freerunner_desc=The _Freerunner_ builds momentum as he runs. Momentum increases his movement speed and evasion, but it quickly fades when he isn't moving.
actors.hero.herosubclass.sniper=sniper