v0.6.2: reworked the assassin subclass
This commit is contained in:
parent
6cda24e6ed
commit
60ca4b0180
Binary file not shown.
Before Width: | Height: | Size: 337 B After Width: | Height: | Size: 337 B |
|
@ -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 );
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue
Block a user