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) { // Do nothing } else { boolean important = ((item instanceof ScrollOfUpgrade || item instanceof ScrollOfEnchantment) && ((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 (Dungeon.isChallenged(Challenges.INSID)) { if (!item.isIdentified()) { item.identify(); } } } if (!heap.isEmpty()) { GLog.i(TXT_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; } }
public Hero() { super(); name = "you"; if (Dungeon.isChallenged(Challenges.MOREHP)) { HP = HT = 2000; } else { HP = HT = 20; } STR = STARTING_STR; awareness = 0.1f; belongings = new Belongings(this); visibleEnemies = new ArrayList<Mob>(); }
public class Hero extends Char { private static final String TXT_LEAVE = "One does not simply leave Pixel Dungeon."; private static final String TXT_LEVEL_UP = "level up!"; private static final String TXT_NEW_LEVEL = "Welcome to level %d! Now you are healthier and more focused. " + "It's easier for you to hit enemies and dodge their attacks."; public static final String TXT_YOU_NOW_HAVE = "You now have %s"; private static final String TXT_SOMETHING_ELSE = "There is something else here"; private static final String TXT_LOCKED_CHEST = "This chest is locked and you don't have matching key"; private static final String TXT_LOCKED_DOOR = "You don't have a matching key"; private static final String TXT_NOTICED_SMTH = "You noticed something"; private static final String TXT_WAIT = "..."; private static final String TXT_SEARCH = "search"; 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 = Dungeon.isChallenged(Challenges.HIGHSKILL) ? 50 : 10; private int defenseSkill = Dungeon.isChallenged(Challenges.HIGHSKILL) ? 40 : 5; public boolean ready = false; public HeroAction curAction = null; public HeroAction lastAction = null; private Char enemy; public Armor.Glyph killerGlyph = null; private Item theKey; public boolean restoreHealth = 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"; if (Dungeon.isChallenged(Challenges.MOREHP)) { HP = HT = 2000; } else { HP = HT = 20; } STR = STARTING_STR; awareness = 0.1f; belongings = new Belongings(this); visibleEnemies = new ArrayList<Mob>(); } public int STR() { 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 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); rangedWeapon = null; return result; } @Override public int attackSkill(Char target) { int bonus = 0; for (Buff buff : buffs(RingOfAccuracy.Accuracy.class)) { bonus += ((RingOfAccuracy.Accuracy) buff).level; } float accuracy = (bonus == 0) ? 1 : (float) Math.pow(1.4, bonus); 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).level; } float evasion = bonus == 0 ? 1 : (float) Math.pow(1.2, bonus); if (paralysed) { evasion /= 2; } int aEnc = belongings.armor != null ? belongings.armor.STR - STR() : 0; if (aEnc > 0) { return (int) (defenseSkill * evasion / Math.pow(1.5, aEnc)); } else { if (heroClass == HeroClass.ROGUE) { if (curAction != null && subClass == HeroSubClass.FREERUNNER && !isStarving()) { evasion *= 2; } 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; if (wep != null) { dmg = wep.damageRoll(this); } else { dmg = STR() > 10 ? Random.IntRange(1, STR() - 9) : 1; } return buff(Fury.class) != null ? (int) (dmg * 1.5f) : dmg; } @Override public float speed() { int aEnc = belongings.armor != null ? belongings.armor.STR - STR() : 0; if (aEnc > 0) { return (float) (super.speed() * Math.pow(1.3, -aEnc)); } else { float speed = super.speed(); return ((HeroSprite) sprite).sprint(subClass == HeroSubClass.FREERUNNER && !isStarving()) ? 1.6f * speed : speed; } } public float attackDelay() { KindOfWeapon wep = rangedWeapon != null ? rangedWeapon : belongings.weapon; if (wep != null) { return wep.speedFactor(this); } else { return 1f; } } @Override public void spend(float time) { int hasteLevel = 0; for (Buff buff : buffs(RingOfHaste.Haste.class)) { hasteLevel += ((RingOfHaste.Haste) buff).level; } super.spend(hasteLevel == 0 ? time : (float) (time * Math.pow(1.1, -hasteLevel))); }; public void spendAndNext(float time) { busy(); spend(time); next(); } @Override public boolean act() { super.act(); if (paralysed) { curAction = null; spendAndNext(TICK); return false; } checkVisibleMobs(); AttackIndicator.updateState(); if (curAction == null) { if (restoreHealth) { if (isStarving() || HP >= HT) { restoreHealth = false; } else { spend(TIME_TO_REST); next(); return false; } } ready(); return false; } else { restoreHealth = 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; ready = true; GameScene.ready(); } public void interrupt() { if (isAlive() && curAction != null && curAction.dst != pos) { lastAction = curAction; } curAction = null; } public void resume() { curAction = lastAction; lastAction = null; 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) { // Do nothing } else { boolean important = ((item instanceof ScrollOfUpgrade || item instanceof ScrollOfEnchantment) && ((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 (Dungeon.isChallenged(Challenges.INSID)) { if (!item.isIdentified()) { item.identify(); } } } if (!heap.isEmpty()) { GLog.i(TXT_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(TXT_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: 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(TXT_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; Hunger hunger = buff(Hunger.class); if (hunger != null && !hunger.isStarving()) { hunger.satisfy(-Hunger.STARVING / 10); } 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(TXT_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.satisfy(-Hunger.STARVING / 10); } 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 tillHealthy) { spendAndNext(TIME_TO_REST); if (!tillHealthy) { sprite.showStatus(CharSprite.DEFAULT, TXT_WAIT); } restoreHealth = tillHealthy; } @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) { damage += Buff.affect(this, Combo.class).hit(enemy, damage); } break; case BATTLEMAGE: if (wep instanceof Wand) { Wand wand = (Wand) wep; if (wand.curCharges >= wand.maxCharges) { wand.use(); } else if (damage > 0) { wand.curCharges++; wand.updateQuickslot(); ScrollOfRecharging.charge(this); } damage += wand.curCharges; } 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) { RingOfThorns.Thorns thorns = buff(RingOfThorns.Thorns.class); if (thorns != null) { int dmg = Random.IntRange(0, damage); if (dmg > 0) { enemy.damage(dmg, thorns); } } Earthroot.Armor armor = buff(Earthroot.Armor.class); if (armor != null) { damage = armor.absorb(damage); } if (belongings.armor != null) { damage = belongings.armor.proc(enemy, this, damage); } return damage; } @Override public void damage(int dmg, Object src) { restoreHealth = false; 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<Mob>(); boolean newMob = false; for (Mob m : Dungeon.level.mobs) { if (Level.fieldOfView[m.pos] && m.hostile) { visible.add(m); if (!visibleEnemies.contains(m)) { newMob = true; } } } if (newMob) { interrupt(); restoreHealth = 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 && !Chasm.jumpConfirmed) { Chasm.heroJump(this); interrupt(); 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) { int oldPos = pos; move(step); sprite.move(oldPos, pos); 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 (Level.fieldOfView[cell] && (heap = Dungeon.level.heaps.get(cell)) != null && heap.type != Heap.Type.HIDDEN) { 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) { 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; boolean levelUp = false; while (this.exp >= maxExp()) { this.exp -= maxExp(); lvl++; HT += 5; HP += 5; attackSkill++; defenseSkill++; if (lvl < 10) { updateAwareness(); } levelUp = true; } if (levelUp) { GLog.p(TXT_NEW_LEVEL, lvl); sprite.showStatus(CharSprite.POSITIVE, TXT_LEVEL_UP); Sample.INSTANCE.play(Assets.SND_LEVELUP); Badges.validateLevelReached(); } if (subClass == HeroSubClass.WARLOCK) { int value = Math.min(HT - HP, 1 + (Dungeon.depth - 1) / 5); if (value > 0) { HP += value; sprite.emitter().burst(Speck.factory(Speck.HEALING), 1); } ((Hunger) buff(Hunger.class)).satisfy(10); } } 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 ((Hunger) buff(Hunger.class)).isStarving(); } @Override public void add(Buff buff) { super.add(buff); 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 away it!"); } 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!"); sprite.showStatus(CharSprite.POSITIVE, "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 Vertigo) { GLog.w("Everything is spinning around you!"); interrupt(); } else if (buff instanceof Light) { sprite.add(CharSprite.State.ILLUMINATED); } } BuffIndicator.refreshHero(); } @Override public void remove(Buff buff) { super.remove(buff); if (buff instanceof Light) { sprite.remove(CharSprite.State.ILLUMINATED); } BuffIndicator.refreshHero(); } @Override public int stealth() { int stealth = super.stealth(); for (Buff buff : buffs(RingOfShadows.Shadows.class)) { stealth += ((RingOfShadows.Shadows) buff).level; } return stealth; } @Override public void die(Object cause) { curAction = null; DewVial.autoDrink(this); if (isAlive()) { new Flare(8, 32).color(0xFFFF66, true).show(sprite, 2f); return; } Actor.fixTime(); super.die(cause); Ankh ankh = (Ankh) belongings.getItem(Ankh.class); 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) { Level.set(i, Terrain.discover(terr)); GameScene.updateMap(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) { 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; for (Buff buff : buffs(RingOfDetection.Detection.class)) { int bonus = ((RingOfDetection.Detection) buff).level; if (bonus > positive) { positive = bonus; } else if (bonus < 0) { negative += bonus; } } 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; } 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); Level.set(p, Terrain.discover(oldValue)); GameScene.updateMap(p); ScrollOfMagicMapping.discover(p); smthFound = true; } if (intentional) { Heap heap = Dungeon.level.heaps.get(p); if (heap != null && heap.type == Type.HIDDEN) { heap.open(this); smthFound = true; } } } } } if (intentional) { sprite.showStatus(CharSprite.DEFAULT, TXT_SEARCH); sprite.operate(pos); if (smthFound) { spendAndNext(Random.Float() < level ? TIME_TO_SEARCH : TIME_TO_SEARCH * 2); } else { spendAndNext(TIME_TO_SEARCH); } } if (smthFound) { GLog.w(TXT_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() { GasesImmunity buff = buff(GasesImmunity.class); return buff == null ? super.immunities() : GasesImmunity.IMMUNITIES; } @Override public void next() { super.next(); } public static interface Doom { public void onDeath(); } }