Files
shattered-pixel-dungeon-web…/src/com/shatteredpixel/shatteredpixeldungeon/actors/hero/Hero.java
T

1521 lines
37 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.hero;
import com.shatteredpixel.shatteredpixeldungeon.Assets;
import com.shatteredpixel.shatteredpixeldungeon.Badges;
import com.shatteredpixel.shatteredpixeldungeon.Bones;
import com.shatteredpixel.shatteredpixeldungeon.Dungeon;
import com.shatteredpixel.shatteredpixeldungeon.GamesInProgress;
import com.shatteredpixel.shatteredpixeldungeon.ResultDescriptions;
import com.shatteredpixel.shatteredpixeldungeon.Statistics;
import com.shatteredpixel.shatteredpixeldungeon.actors.Actor;
import com.shatteredpixel.shatteredpixeldungeon.actors.Char;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Barkskin;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Bleeding;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Bless;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Blindness;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Burning;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Charm;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Combo;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Cripple;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Drowsy;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Fury;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Hunger;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Invisibility;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Ooze;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Paralysis;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Poison;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Regeneration;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Roots;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.SnipersMark;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Vertigo;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Weakness;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mob;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.NPC;
import com.shatteredpixel.shatteredpixeldungeon.effects.CellEmitter;
import com.shatteredpixel.shatteredpixeldungeon.effects.CheckedCell;
import com.shatteredpixel.shatteredpixeldungeon.effects.Flare;
import com.shatteredpixel.shatteredpixeldungeon.effects.Speck;
import com.shatteredpixel.shatteredpixeldungeon.items.Amulet;
import com.shatteredpixel.shatteredpixeldungeon.items.Ankh;
import com.shatteredpixel.shatteredpixeldungeon.items.Dewdrop;
import com.shatteredpixel.shatteredpixeldungeon.items.Heap;
import com.shatteredpixel.shatteredpixeldungeon.items.Heap.Type;
import com.shatteredpixel.shatteredpixeldungeon.items.Item;
import com.shatteredpixel.shatteredpixeldungeon.items.KindOfWeapon;
import com.shatteredpixel.shatteredpixeldungeon.items.armor.glyphs.Viscosity;
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.CapeOfThorns;
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.DriedRose;
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.EtherealChains;
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.TalismanOfForesight;
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.TimekeepersHourglass;
import com.shatteredpixel.shatteredpixeldungeon.items.keys.GoldenKey;
import com.shatteredpixel.shatteredpixeldungeon.items.keys.IronKey;
import com.shatteredpixel.shatteredpixeldungeon.items.keys.Key;
import com.shatteredpixel.shatteredpixeldungeon.items.keys.SkeletonKey;
import com.shatteredpixel.shatteredpixeldungeon.items.potions.Potion;
import com.shatteredpixel.shatteredpixeldungeon.items.potions.PotionOfMight;
import com.shatteredpixel.shatteredpixeldungeon.items.potions.PotionOfStrength;
import com.shatteredpixel.shatteredpixeldungeon.items.rings.RingOfElements;
import com.shatteredpixel.shatteredpixeldungeon.items.rings.RingOfEvasion;
import com.shatteredpixel.shatteredpixeldungeon.items.rings.RingOfForce;
import com.shatteredpixel.shatteredpixeldungeon.items.rings.RingOfFuror;
import com.shatteredpixel.shatteredpixeldungeon.items.rings.RingOfHaste;
import com.shatteredpixel.shatteredpixeldungeon.items.rings.RingOfMight;
import com.shatteredpixel.shatteredpixeldungeon.items.rings.RingOfTenacity;
import com.shatteredpixel.shatteredpixeldungeon.items.scrolls.Scroll;
import com.shatteredpixel.shatteredpixeldungeon.items.scrolls.ScrollOfMagicMapping;
import com.shatteredpixel.shatteredpixeldungeon.items.scrolls.ScrollOfUpgrade;
import com.shatteredpixel.shatteredpixeldungeon.items.scrolls.ScrollOfMagicalInfusion;
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.melee.MeleeWeapon;
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.missiles.MissileWeapon;
import com.shatteredpixel.shatteredpixeldungeon.levels.Level;
import com.shatteredpixel.shatteredpixeldungeon.levels.Terrain;
import com.shatteredpixel.shatteredpixeldungeon.levels.features.AlchemyPot;
import com.shatteredpixel.shatteredpixeldungeon.levels.features.Chasm;
import com.shatteredpixel.shatteredpixeldungeon.levels.features.Sign;
import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
import com.shatteredpixel.shatteredpixeldungeon.plants.Earthroot;
import com.shatteredpixel.shatteredpixeldungeon.plants.Sungrass;
import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene;
import com.shatteredpixel.shatteredpixeldungeon.scenes.InterlevelScene;
import com.shatteredpixel.shatteredpixeldungeon.scenes.SurfaceScene;
import com.shatteredpixel.shatteredpixeldungeon.sprites.CharSprite;
import com.shatteredpixel.shatteredpixeldungeon.sprites.HeroSprite;
import com.shatteredpixel.shatteredpixeldungeon.ui.AttackIndicator;
import com.shatteredpixel.shatteredpixeldungeon.ui.BuffIndicator;
import com.shatteredpixel.shatteredpixeldungeon.ui.QuickSlotButton;
import com.shatteredpixel.shatteredpixeldungeon.utils.GLog;
import com.shatteredpixel.shatteredpixeldungeon.windows.WndMessage;
import com.shatteredpixel.shatteredpixeldungeon.windows.WndResurrect;
import com.shatteredpixel.shatteredpixeldungeon.windows.WndTradeItem;
import com.watabou.noosa.Camera;
import com.watabou.noosa.Game;
import com.watabou.noosa.audio.Sample;
import com.watabou.utils.Bundle;
import com.watabou.utils.Random;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
public class Hero extends Char {
{
actPriority = 0; //acts at priority 0, baseline for the rest of behaviour.
}
public static final int MAX_LEVEL = 30;
//TODO: remove this static variable
public static final String TXT_YOU_NOW_HAVE = Messages.get(Hero.class, "you_now_have");
public static final int STARTING_STR = 10;
private static final float TIME_TO_REST = 1f;
private static final float TIME_TO_SEARCH = 2f;
public HeroClass heroClass = HeroClass.ROGUE;
public HeroSubClass subClass = HeroSubClass.NONE;
private int attackSkill = 10;
private int defenseSkill = 5;
public boolean ready = false;
private boolean damageInterrupt = true;
public HeroAction curAction = null;
public HeroAction lastAction = null;
private Char enemy;
private Item theKey;
public boolean resting = false;
public MissileWeapon rangedWeapon = null;
public Belongings belongings;
public int STR;
public boolean weakened = false;
public float awareness;
public int lvl = 1;
public int exp = 0;
private ArrayList<Mob> visibleEnemies;
public Hero() {
super();
name = "you";
HP = HT = 20;
STR = STARTING_STR;
awareness = 0.1f;
belongings = new Belongings( this );
visibleEnemies = new ArrayList<Mob>();
}
public int STR() {
int STR = this.STR;
for (Buff buff : buffs(RingOfMight.Might.class)) {
STR += ((RingOfMight.Might)buff).level;
}
return weakened ? STR - 2 : STR;
}
private static final String ATTACK = "attackSkill";
private static final String DEFENSE = "defenseSkill";
private static final String STRENGTH = "STR";
private static final String LEVEL = "lvl";
private static final String EXPERIENCE = "exp";
@Override
public void storeInBundle( Bundle bundle ) {
super.storeInBundle( bundle );
heroClass.storeInBundle( bundle );
subClass.storeInBundle( bundle );
bundle.put( ATTACK, attackSkill );
bundle.put( DEFENSE, defenseSkill );
bundle.put( STRENGTH, STR );
bundle.put( LEVEL, lvl );
bundle.put( EXPERIENCE, exp );
belongings.storeInBundle( bundle );
}
@Override
public void restoreFromBundle( Bundle bundle ) {
super.restoreFromBundle( bundle );
heroClass = HeroClass.restoreInBundle( bundle );
subClass = HeroSubClass.restoreInBundle( bundle );
attackSkill = bundle.getInt( ATTACK );
defenseSkill = bundle.getInt( DEFENSE );
STR = bundle.getInt( STRENGTH );
updateAwareness();
lvl = bundle.getInt( LEVEL );
exp = bundle.getInt( EXPERIENCE );
belongings.restoreFromBundle( bundle );
}
public static void preview( GamesInProgress.Info info, Bundle bundle ) {
info.level = bundle.getInt( LEVEL );
}
public String className() {
return subClass == null || subClass == HeroSubClass.NONE ? heroClass.title() : subClass.title();
}
public String givenName(){
return name.equals("you") ? className() : name;
}
public void live() {
Buff.affect( this, Regeneration.class );
Buff.affect( this, Hunger.class );
}
public int tier() {
return belongings.armor == null ? 0 : belongings.armor.tier;
}
public boolean shoot( Char enemy, MissileWeapon wep ) {
rangedWeapon = wep;
boolean result = attack( enemy );
Invisibility.dispel();
rangedWeapon = null;
return result;
}
@Override
public int attackSkill( Char target ) {
float accuracy = 1;
if (rangedWeapon != null && Level.distance( pos, target.pos ) == 1) {
accuracy *= 0.5f;
}
KindOfWeapon wep = rangedWeapon != null ? rangedWeapon : belongings.weapon;
if (wep != null) {
return (int)(attackSkill * accuracy * wep.acuracyFactor( this ));
} else {
return (int)(attackSkill * accuracy);
}
}
@Override
public int defenseSkill( Char enemy ) {
int bonus = 0;
for (Buff buff : buffs( RingOfEvasion.Evasion.class )) {
bonus += ((RingOfEvasion.Evasion)buff).effectiveLevel;
}
float evasion = (float)Math.pow( 1.15, bonus );
if (paralysed > 0) {
evasion /= 2;
}
int aEnc = belongings.armor != null ? belongings.armor.STR - STR() : 9 - STR();
if (aEnc > 0) {
return (int)(defenseSkill * evasion / Math.pow( 1.5, aEnc ));
} else {
if (heroClass == HeroClass.ROGUE) {
return (int)((defenseSkill - aEnc) * evasion);
} else {
return (int)(defenseSkill * evasion);
}
}
}
@Override
public int dr() {
int dr = belongings.armor != null ? Math.max( belongings.armor.DR(), 0 ) : 0;
Barkskin barkskin = buff( Barkskin.class );
if (barkskin != null) {
dr += barkskin.level();
}
return dr;
}
@Override
public int damageRoll() {
KindOfWeapon wep = rangedWeapon != null ? rangedWeapon : belongings.weapon;
int dmg;
int bonus = 0;
for (Buff buff : buffs( RingOfForce.Force.class )) {
bonus += ((RingOfForce.Force)buff).level;
}
if (wep != null) {
dmg = wep.damageRoll( this ) + bonus;
} else {
int str = STR() - 8;
dmg = bonus == 0 ?
str > 1 ? Random.NormalIntRange( 1, str ) : 1
: bonus > 0 ?
str > 0 ? Random.NormalIntRange( str/2+bonus, (int)(str*0.5f*bonus) + str*2 ) : 1
: 0;
}
if (dmg < 0) dmg = 0;
return buff( Fury.class ) != null ? (int)(dmg * 1.5f) : dmg;
}
@Override
public float speed() {
float speed = super.speed();
int hasteLevel = 0;
for (Buff buff : buffs( RingOfHaste.Haste.class )) {
hasteLevel += ((RingOfHaste.Haste)buff).level;
}
if (hasteLevel != 0)
speed *= Math.pow(1.2, hasteLevel);
int aEnc = belongings.armor != null ? belongings.armor.STR - STR() : 0;
if (aEnc > 0) {
return (float)(speed * Math.pow( 1.3, -aEnc ));
} else {
return ((HeroSprite)sprite).sprint( subClass == HeroSubClass.FREERUNNER && !isStarving() ) ?
invisible > 0 ?
2f * speed :
1.5f * speed :
speed;
}
}
public float attackDelay() {
KindOfWeapon wep = rangedWeapon != null ? rangedWeapon : belongings.weapon;
if (wep != null) {
return wep.speedFactor( this );
} else {
//Normally putting furor speed on unarmed attacks would be unnecessary
//But there's going to be that one guy who gets a furor+force ring combo
//This is for that one guy, you shall get your fists of fury!
int bonus = 0;
for (Buff buff : buffs(RingOfFuror.Furor.class)) {
bonus += ((RingOfFuror.Furor)buff).level;
}
return (float)(0.25 + (1 - 0.25)*Math.pow(0.8, bonus));
}
}
@Override
public void spend( float time ) {
TimekeepersHourglass.timeFreeze buff = buff(TimekeepersHourglass.timeFreeze.class);
if (!(buff != null && buff.processTime(time)))
super.spend( time );
}
public void spendAndNext( float time ) {
busy();
spend( time );
next();
}
@Override
public boolean act() {
super.act();
if (paralysed > 0) {
curAction = null;
spendAndNext( TICK );
return false;
}
checkVisibleMobs();
if (curAction == null) {
if (resting) {
spend( TIME_TO_REST ); next();
return false;
}
ready();
return false;
} else {
resting = false;
ready = false;
if (curAction instanceof HeroAction.Move) {
return actMove( (HeroAction.Move)curAction );
} else
if (curAction instanceof HeroAction.Interact) {
return actInteract( (HeroAction.Interact)curAction );
} else
if (curAction instanceof HeroAction.Buy) {
return actBuy( (HeroAction.Buy)curAction );
}else
if (curAction instanceof HeroAction.PickUp) {
return actPickUp( (HeroAction.PickUp)curAction );
} else
if (curAction instanceof HeroAction.OpenChest) {
return actOpenChest( (HeroAction.OpenChest)curAction );
} else
if (curAction instanceof HeroAction.Unlock) {
return actUnlock((HeroAction.Unlock) curAction);
} else
if (curAction instanceof HeroAction.Descend) {
return actDescend( (HeroAction.Descend)curAction );
} else
if (curAction instanceof HeroAction.Ascend) {
return actAscend( (HeroAction.Ascend)curAction );
} else
if (curAction instanceof HeroAction.Attack) {
return actAttack( (HeroAction.Attack)curAction );
} else
if (curAction instanceof HeroAction.Cook) {
return actCook( (HeroAction.Cook)curAction );
}
}
return false;
}
public void busy() {
ready = false;
}
private void ready() {
sprite.idle();
curAction = null;
damageInterrupt = true;
ready = true;
AttackIndicator.updateState();
GameScene.ready();
}
public void interrupt() {
if (isAlive() && curAction != null && curAction instanceof HeroAction.Move && curAction.dst != pos) {
lastAction = curAction;
}
curAction = null;
}
public void resume() {
curAction = lastAction;
lastAction = null;
damageInterrupt = false;
act();
}
private boolean actMove( HeroAction.Move action ) {
if (getCloser( action.dst )) {
return true;
} else {
if (Dungeon.level.map[pos] == Terrain.SIGN) {
Sign.read(pos);
}
ready();
return false;
}
}
private boolean actInteract( HeroAction.Interact action ) {
NPC npc = action.npc;
if (Level.adjacent( pos, npc.pos )) {
ready();
sprite.turnTo( pos, npc.pos );
npc.interact();
return false;
} else {
if (Level.fieldOfView[npc.pos] && getCloser( npc.pos )) {
return true;
} else {
ready();
return false;
}
}
}
private boolean actBuy( HeroAction.Buy action ) {
int dst = action.dst;
if (pos == dst || Level.adjacent( pos, dst )) {
ready();
Heap heap = Dungeon.level.heaps.get( dst );
if (heap != null && heap.type == Type.FOR_SALE && heap.size() == 1) {
GameScene.show( new WndTradeItem( heap, true ) );
}
return false;
} else if (getCloser( dst )) {
return true;
} else {
ready();
return false;
}
}
private boolean actCook( HeroAction.Cook action ) {
int dst = action.dst;
if (Dungeon.visible[dst]) {
ready();
AlchemyPot.operate( this, dst );
return false;
} else if (getCloser( dst )) {
return true;
} else {
ready();
return false;
}
}
private boolean actPickUp( HeroAction.PickUp action ) {
int dst = action.dst;
if (pos == dst) {
Heap heap = Dungeon.level.heaps.get( pos );
if (heap != null) {
Item item = heap.pickUp();
if (item.doPickUp( this )) {
if (item instanceof Dewdrop
|| item instanceof TimekeepersHourglass.sandBag
|| item instanceof DriedRose.Petal) {
//Do Nothing
} else {
boolean important =
((item instanceof ScrollOfUpgrade || item instanceof ScrollOfMagicalInfusion) && ((Scroll)item).isKnown()) ||
((item instanceof PotionOfStrength || item instanceof PotionOfMight) && ((Potion)item).isKnown());
if (important) {
GLog.p( TXT_YOU_NOW_HAVE, item.name() );
} else {
GLog.i( TXT_YOU_NOW_HAVE, item.name() );
}
}
if (!heap.isEmpty()) {
GLog.i( Messages.get(this, "something_else") );
}
curAction = null;
} else {
Dungeon.level.drop( item, pos ).sprite.drop();
ready();
}
} else {
ready();
}
return false;
} else if (getCloser( dst )) {
return true;
} else {
ready();
return false;
}
}
private boolean actOpenChest( HeroAction.OpenChest action ) {
int dst = action.dst;
if (Level.adjacent( pos, dst ) || pos == dst) {
Heap heap = Dungeon.level.heaps.get( dst );
if (heap != null && (heap.type != Type.HEAP && heap.type != Type.FOR_SALE)) {
theKey = null;
if (heap.type == Type.LOCKED_CHEST || heap.type == Type.CRYSTAL_CHEST) {
theKey = belongings.getKey( GoldenKey.class, Dungeon.depth );
if (theKey == null) {
GLog.w( Messages.get(this, "locked_chest") );
ready();
return false;
}
}
switch (heap.type) {
case TOMB:
Sample.INSTANCE.play( Assets.SND_TOMB );
Camera.main.shake( 1, 0.5f );
break;
case SKELETON:
case REMAINS:
break;
default:
Sample.INSTANCE.play( Assets.SND_UNLOCK );
}
spend( Key.TIME_TO_UNLOCK );
sprite.operate( dst );
} else {
ready();
}
return false;
} else if (getCloser( dst )) {
return true;
} else {
ready();
return false;
}
}
private boolean actUnlock( HeroAction.Unlock action ) {
int doorCell = action.dst;
if (Level.adjacent( pos, doorCell )) {
theKey = null;
int door = Dungeon.level.map[doorCell];
if (door == Terrain.LOCKED_DOOR) {
theKey = belongings.getKey( IronKey.class, Dungeon.depth );
} else if (door == Terrain.LOCKED_EXIT) {
theKey = belongings.getKey( SkeletonKey.class, Dungeon.depth );
}
if (theKey != null) {
spend( Key.TIME_TO_UNLOCK );
sprite.operate( doorCell );
Sample.INSTANCE.play( Assets.SND_UNLOCK );
} else {
GLog.w( Messages.get(this, "locked_door") );
ready();
}
return false;
} else if (getCloser( doorCell )) {
return true;
} else {
ready();
return false;
}
}
private boolean actDescend( HeroAction.Descend action ) {
int stairs = action.dst;
if (pos == stairs && pos == Dungeon.level.exit) {
curAction = null;
Buff buff = buff(TimekeepersHourglass.timeFreeze.class);
if (buff != null) buff.detach();
for (Mob mob : Dungeon.level.mobs.toArray( new Mob[0] ))
if (mob instanceof DriedRose.GhostHero) mob.destroy();
InterlevelScene.mode = InterlevelScene.Mode.DESCEND;
Game.switchScene( InterlevelScene.class );
return false;
} else if (getCloser( stairs )) {
return true;
} else {
ready();
return false;
}
}
private boolean actAscend( HeroAction.Ascend action ) {
int stairs = action.dst;
if (pos == stairs && pos == Dungeon.level.entrance) {
if (Dungeon.depth == 1) {
if (belongings.getItem( Amulet.class ) == null) {
GameScene.show( new WndMessage( Messages.get(this, "leave") ) );
ready();
} else {
Dungeon.win( ResultDescriptions.WIN );
Dungeon.deleteGame( Dungeon.hero.heroClass, true );
Game.switchScene( SurfaceScene.class );
}
} else {
curAction = null;
Hunger hunger = buff( Hunger.class );
if (hunger != null && !hunger.isStarving()) {
hunger.reduceHunger( -Hunger.STARVING / 10 );
}
Buff buff = buff(TimekeepersHourglass.timeFreeze.class);
if (buff != null) buff.detach();
for (Mob mob : Dungeon.level.mobs.toArray( new Mob[0] ))
if (mob instanceof DriedRose.GhostHero) mob.destroy();
InterlevelScene.mode = InterlevelScene.Mode.ASCEND;
Game.switchScene( InterlevelScene.class );
}
return false;
} else if (getCloser( stairs )) {
return true;
} else {
ready();
return false;
}
}
private boolean actAttack( HeroAction.Attack action ) {
enemy = action.target;
if (Level.adjacent( pos, enemy.pos ) && enemy.isAlive() && !isCharmedBy( enemy )) {
spend( attackDelay() );
sprite.attack( enemy.pos );
return false;
} else {
if (Level.fieldOfView[enemy.pos] && getCloser( enemy.pos )) {
return true;
} else {
ready();
return false;
}
}
}
public void rest( boolean fullRest ) {
spendAndNext( TIME_TO_REST );
if (!fullRest) {
sprite.showStatus( CharSprite.DEFAULT, Messages.get(this, "wait") );
}
resting = fullRest;
}
@Override
public int attackProc( Char enemy, int damage ) {
KindOfWeapon wep = rangedWeapon != null ? rangedWeapon : belongings.weapon;
if (wep != null) wep.proc( this, enemy, damage );
switch (subClass) {
case GLADIATOR:
if (wep instanceof MeleeWeapon || wep == null) {
damage += Buff.affect( this, Combo.class ).hit( enemy, damage );
}
break;
case SNIPER:
if (rangedWeapon != null) {
Buff.prolong( this, SnipersMark.class, attackDelay() * 1.1f ).object = enemy.id();
}
break;
default:
}
return damage;
}
@Override
public int defenseProc( Char enemy, int damage ) {
Earthroot.Armor armor = buff( Earthroot.Armor.class );
if (armor != null) {
damage = armor.absorb( damage );
}
Sungrass.Health health = buff( Sungrass.Health.class );
if (health != null) {
health.absorb( damage );
}
if (belongings.armor != null) {
damage = belongings.armor.proc( enemy, this, damage );
}
return damage;
}
@Override
public void damage( int dmg, Object src ) {
if (buff(TimekeepersHourglass.timeStasis.class) != null)
return;
if (!(src instanceof Hunger || src instanceof Viscosity.DeferedDamage) && damageInterrupt) {
interrupt();
resting = false;
}
if (this.buff(Drowsy.class) != null){
Buff.detach(this, Drowsy.class);
GLog.w( Messages.get(this, "pain_resist") );
}
CapeOfThorns.Thorns thorns = buff( CapeOfThorns.Thorns.class );
if (thorns != null) {
dmg = thorns.proc(dmg, (src instanceof Char ? (Char)src : null), this);
}
int tenacity = 0;
for (Buff buff : buffs(RingOfTenacity.Tenacity.class)) {
tenacity += ((RingOfTenacity.Tenacity)buff).level;
}
if (tenacity != 0) //(HT - HP)/HT = heroes current % missing health.
dmg = (int)Math.ceil((float)dmg * Math.pow(0.9, tenacity*((float)(HT - HP)/HT)));
super.damage( dmg, src );
if (subClass == HeroSubClass.BERSERKER && 0 < HP && HP <= HT * Fury.LEVEL) {
Buff.affect( this, Fury.class );
}
}
private void checkVisibleMobs() {
ArrayList<Mob> visible = new ArrayList<>();
boolean newMob = false;
Mob target = null;
for (Mob m : Dungeon.level.mobs) {
if (Level.fieldOfView[ m.pos ] && m.hostile) {
visible.add(m);
if (!visibleEnemies.contains( m )) {
newMob = true;
}
if (QuickSlotButton.autoAim(m) != -1){
if (target == null){
target = m;
} else if (distance(target) > distance(m)) {
target = m;
}
}
}
}
if (target != null && (QuickSlotButton.lastTarget == null ||
!QuickSlotButton.lastTarget.isAlive() ||
!Dungeon.visible[QuickSlotButton.lastTarget.pos])){
QuickSlotButton.target(target);
}
if (newMob) {
interrupt();
resting = false;
}
visibleEnemies = visible;
}
public int visibleEnemies() {
return visibleEnemies.size();
}
public Mob visibleEnemy( int index ) {
return visibleEnemies.get(index % visibleEnemies.size());
}
private boolean getCloser( final int target ) {
if (rooted) {
Camera.main.shake( 1, 1f );
return false;
}
int step = -1;
if (Level.adjacent( pos, target )) {
if (Actor.findChar( target ) == null) {
if (Level.pit[target] && !flying && !Level.solid[target]) {
if (!Chasm.jumpConfirmed){
Chasm.heroJump(this);
interrupt();
} else {
Chasm.heroFall(target);
}
return false;
}
if (Level.passable[target] || Level.avoid[target]) {
step = target;
}
}
} else {
int len = Level.LENGTH;
boolean[] p = Level.passable;
boolean[] v = Dungeon.level.visited;
boolean[] m = Dungeon.level.mapped;
boolean[] passable = new boolean[len];
for (int i=0; i < len; i++) {
passable[i] = p[i] && (v[i] || m[i]);
}
step = Dungeon.findPath( this, pos, target, passable, Level.fieldOfView );
}
if (step != -1) {
sprite.move(pos, step);
move(step);
spend( 1 / speed() );
return true;
} else {
return false;
}
}
public boolean handle( int cell ) {
if (cell == -1) {
return false;
}
Char ch;
Heap heap;
if (Dungeon.level.map[cell] == Terrain.ALCHEMY && cell != pos) {
curAction = new HeroAction.Cook( cell );
} else if (Level.fieldOfView[cell] && (ch = Actor.findChar( cell )) instanceof Mob) {
if (ch instanceof NPC) {
curAction = new HeroAction.Interact( (NPC)ch );
} else {
curAction = new HeroAction.Attack( ch );
}
} else if ((heap = Dungeon.level.heaps.get( cell )) != null
//moving to an item doesn't auto-pickup when enemies are near...
&& (visibleEnemies.size() == 0 || cell == pos ||
//...but only for standard heaps, chests and similar open as normal.
(heap.type != Type.HEAP && heap.type != Type.FOR_SALE))) {
switch (heap.type) {
case HEAP:
curAction = new HeroAction.PickUp( cell );
break;
case FOR_SALE:
curAction = heap.size() == 1 && heap.peek().price() > 0 ?
new HeroAction.Buy( cell ) :
new HeroAction.PickUp( cell );
break;
default:
curAction = new HeroAction.OpenChest( cell );
}
} else if (Dungeon.level.map[cell] == Terrain.LOCKED_DOOR || Dungeon.level.map[cell] == Terrain.LOCKED_EXIT) {
curAction = new HeroAction.Unlock( cell );
} else if (cell == Dungeon.level.exit && Dungeon.depth < 26) {
curAction = new HeroAction.Descend( cell );
} else if (cell == Dungeon.level.entrance) {
curAction = new HeroAction.Ascend( cell );
} else {
curAction = new HeroAction.Move( cell );
lastAction = null;
}
return act();
}
public void earnExp( int exp ) {
this.exp += exp;
float percent = exp/(float)maxExp();
EtherealChains.chainsRecharge chains = buff(EtherealChains.chainsRecharge.class);
if (chains != null) chains.gainExp(percent);
boolean levelUp = false;
while (this.exp >= maxExp()) {
this.exp -= maxExp();
if (lvl < MAX_LEVEL) {
lvl++;
levelUp = true;
HT += 5;
HP += 5;
attackSkill++;
defenseSkill++;
} else {
Buff.prolong(this, Bless.class, 30f);
this.exp = 0;
GLog.p( Messages.get(this, "level_cap"));
Sample.INSTANCE.play( Assets.SND_LEVELUP );
}
if (lvl < 10) {
updateAwareness();
}
}
if (levelUp) {
GLog.p( Messages.get(this, "new_level"), lvl );
sprite.showStatus( CharSprite.POSITIVE, Messages.get(Hero.class, "level_up") );
Sample.INSTANCE.play( Assets.SND_LEVELUP );
Badges.validateLevelReached();
}
}
public int maxExp() {
return 5 + lvl * 5;
}
void updateAwareness() {
awareness = (float)(1 - Math.pow(
(heroClass == HeroClass.ROGUE ? 0.85 : 0.90),
(1 + Math.min( lvl, 9 )) * 0.5
));
}
public boolean isStarving() {
return buff(Hunger.class) != null && ((Hunger)buff( Hunger.class )).isStarving();
}
@Override
public void add( Buff buff ) {
if (buff(TimekeepersHourglass.timeStasis.class) != null)
return;
super.add( buff );
//TODO: need to do something with these so they can be i18n-ified
if (sprite != null) {
if (buff instanceof Burning) {
GLog.w( "You catch fire!" );
interrupt();
} else if (buff instanceof Paralysis) {
GLog.w( "You are paralysed!" );
interrupt();
} else if (buff instanceof Poison) {
GLog.w( "You are poisoned!" );
interrupt();
} else if (buff instanceof Ooze) {
GLog.w( "Caustic ooze eats your flesh. Wash it away!" );
} else if (buff instanceof Roots) {
GLog.w( "You can't move!" );
} else if (buff instanceof Weakness) {
GLog.w( "You feel weakened!" );
} else if (buff instanceof Blindness) {
GLog.w( "You are blinded!" );
} else if (buff instanceof Fury) {
GLog.w( "You become furious!" );
} else if (buff instanceof Charm) {
GLog.w( "You are charmed!" );
} else if (buff instanceof Cripple) {
GLog.w( "You are crippled!" );
} else if (buff instanceof Bleeding) {
GLog.w( "You are bleeding!" );
} else if (buff instanceof RingOfMight.Might){
if (((RingOfMight.Might)buff).level > 0) {
HT += ((RingOfMight.Might) buff).level * 5;
}
} else if (buff instanceof Vertigo) {
GLog.w("Everything is spinning around you!");
interrupt();
}
}
BuffIndicator.refreshHero();
}
@Override
public void remove( Buff buff ) {
super.remove( buff );
if (buff instanceof RingOfMight.Might){
if (((RingOfMight.Might)buff).level > 0){
HT -= ((RingOfMight.Might) buff).level * 5;
HP = Math.min(HT, HP);
}
}
BuffIndicator.refreshHero();
}
@Override
public int stealth() {
int stealth = super.stealth();
for (Buff buff : buffs( RingOfEvasion.Evasion.class )) {
stealth += ((RingOfEvasion.Evasion)buff).effectiveLevel;
}
return stealth;
}
@Override
public void die( Object cause ) {
curAction = null;
Ankh ankh = null;
//look for ankhs in player inventory, prioritize ones which are blessed.
for (Item item : belongings){
if (item instanceof Ankh) {
if (ankh == null || ((Ankh) item).isBlessed()) {
ankh = (Ankh) item;
}
}
}
if (ankh != null && ankh.isBlessed()) {
this.HP = HT/4;
//ensures that you'll get to act first in almost any case, to prevent reviving and then instantly dieing again.
Buff.detach(this, Paralysis.class);
spend(-cooldown());
new Flare(8, 32).color(0xFFFF66, true).show(sprite, 2f);
CellEmitter.get(this.pos).start(Speck.factory(Speck.LIGHT), 0.2f, 3);
ankh.detach(belongings.backpack);
Sample.INSTANCE.play( Assets.SND_TELEPORT );
GLog.w( ankh.TXT_REVIVE );
Statistics.ankhsUsed++;
return;
}
Actor.fixTime();
super.die( cause );
if (ankh == null) {
reallyDie( cause );
} else {
Dungeon.deleteGame( Dungeon.hero.heroClass, false );
GameScene.show( new WndResurrect( ankh, cause ) );
}
}
public static void reallyDie( Object cause ) {
int length = Level.LENGTH;
int[] map = Dungeon.level.map;
boolean[] visited = Dungeon.level.visited;
boolean[] discoverable = Level.discoverable;
for (int i=0; i < length; i++) {
int terr = map[i];
if (discoverable[i]) {
visited[i] = true;
if ((Terrain.flags[terr] & Terrain.SECRET) != 0) {
Dungeon.level.discover( i );
}
}
}
Bones.leave();
Dungeon.observe();
Dungeon.hero.belongings.identify();
int pos = Dungeon.hero.pos;
ArrayList<Integer> passable = new ArrayList<Integer>();
for (Integer ofs : Level.NEIGHBOURS8) {
int cell = pos + ofs;
if ((Level.passable[cell] || Level.avoid[cell]) && Dungeon.level.heaps.get( cell ) == null) {
passable.add( cell );
}
}
Collections.shuffle( passable );
ArrayList<Item> items = new ArrayList<Item>( Dungeon.hero.belongings.backpack.items );
for (Integer cell : passable) {
if (items.isEmpty()) {
break;
}
Item item = Random.element( items );
Dungeon.level.drop( item, cell ).sprite.drop( pos );
items.remove( item );
}
GameScene.gameOver();
if (cause instanceof Hero.Doom) {
((Hero.Doom)cause).onDeath();
}
Dungeon.deleteGame( Dungeon.hero.heroClass, true );
}
@Override
public void move( int step ) {
super.move( step );
if (!flying) {
if (Level.water[pos]) {
Sample.INSTANCE.play( Assets.SND_WATER, 1, 1, Random.Float( 0.8f, 1.25f ) );
} else {
Sample.INSTANCE.play( Assets.SND_STEP );
}
Dungeon.level.press(pos, this);
}
}
@Override
public void onMotionComplete() {
Dungeon.observe();
search( false );
super.onMotionComplete();
}
@Override
public void onAttackComplete() {
AttackIndicator.target(enemy);
attack( enemy );
curAction = null;
Invisibility.dispel();
super.onAttackComplete();
}
@Override
public void onOperateComplete() {
if (curAction instanceof HeroAction.Unlock) {
if (theKey != null) {
theKey.detach( belongings.backpack );
theKey = null;
}
int doorCell = ((HeroAction.Unlock)curAction).dst;
int door = Dungeon.level.map[doorCell];
Level.set( doorCell, door == Terrain.LOCKED_DOOR ? Terrain.DOOR : Terrain.UNLOCKED_EXIT );
GameScene.updateMap( doorCell );
} else if (curAction instanceof HeroAction.OpenChest) {
if (theKey != null) {
theKey.detach( belongings.backpack );
theKey = null;
}
Heap heap = Dungeon.level.heaps.get( ((HeroAction.OpenChest)curAction).dst );
if (heap.type == Type.SKELETON || heap.type == Type.REMAINS) {
Sample.INSTANCE.play( Assets.SND_BONES );
}
heap.open( this );
}
curAction = null;
super.onOperateComplete();
}
public boolean search( boolean intentional ) {
boolean smthFound = false;
int positive = 0;
int negative = 0;
int distance = 1 + positive + negative;
float level = intentional ? (2 * awareness - awareness * awareness) : awareness;
if (distance <= 0) {
level /= 2 - distance;
distance = 1;
}
int cx = pos % Level.WIDTH;
int cy = pos / Level.WIDTH;
int ax = cx - distance;
if (ax < 0) {
ax = 0;
}
int bx = cx + distance;
if (bx >= Level.WIDTH) {
bx = Level.WIDTH - 1;
}
int ay = cy - distance;
if (ay < 0) {
ay = 0;
}
int by = cy + distance;
if (by >= Level.HEIGHT) {
by = Level.HEIGHT - 1;
}
TalismanOfForesight.Foresight foresight = buff( TalismanOfForesight.Foresight.class );
//cursed talisman of foresight makes unintentionally finding things impossible.
if (foresight != null && foresight.isCursed()){
level = -1;
}
for (int y = ay; y <= by; y++) {
for (int x = ax, p = ax + y * Level.WIDTH; x <= bx; x++, p++) {
if (Dungeon.visible[p]) {
if (intentional) {
sprite.parent.addToBack( new CheckedCell( p ) );
}
if (Level.secret[p] && (intentional || Random.Float() < level)) {
int oldValue = Dungeon.level.map[p];
GameScene.discoverTile( p, oldValue );
Dungeon.level.discover( p );
ScrollOfMagicMapping.discover( p );
smthFound = true;
if (foresight != null && !foresight.isCursed())
foresight.charge();
}
}
}
}
if (intentional) {
sprite.showStatus( CharSprite.DEFAULT, Messages.get(this, "search") );
sprite.operate( pos );
if (foresight != null && foresight.isCursed()){
GLog.n(Messages.get(this, "search_distracted"));
spendAndNext(TIME_TO_SEARCH * 3);
} else {
spendAndNext(TIME_TO_SEARCH);
}
}
if (smthFound) {
GLog.w( Messages.get(this, "noticed_smth") );
Sample.INSTANCE.play( Assets.SND_SECRET );
interrupt();
}
return smthFound;
}
public void resurrect( int resetLevel ) {
HP = HT;
Dungeon.gold = 0;
exp = 0;
belongings.resurrect( resetLevel );
live();
}
@Override
public HashSet<Class<?>> resistances() {
RingOfElements.Resistance r = buff( RingOfElements.Resistance.class );
return r == null ? super.resistances() : r.resistances();
}
@Override
public HashSet<Class<?>> immunities() {
HashSet<Class<?>> immunities = new HashSet<Class<?>>();
for (Buff buff : buffs()){
for (Class<?> immunity : buff.immunities)
immunities.add(immunity);
}
return immunities;
}
@Override
public void next() {
super.next();
}
public static interface Doom {
public void onDeath();
}
}