/* * 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 */ package com.shatteredpixel.shatteredpixeldungeon.actors.mobs; import java.util.ArrayList; import java.util.HashSet; import com.shatteredpixel.shatteredpixeldungeon.Dungeon; import com.shatteredpixel.shatteredpixeldungeon.ResultDescriptions; import com.shatteredpixel.shatteredpixeldungeon.actors.Actor; import com.shatteredpixel.shatteredpixeldungeon.actors.Char; import com.shatteredpixel.shatteredpixeldungeon.actors.blobs.Blob; import com.shatteredpixel.shatteredpixeldungeon.actors.blobs.Fire; import com.shatteredpixel.shatteredpixeldungeon.actors.blobs.ToxicGas; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Amok; 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.LockedFloor; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Ooze; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Poison; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Sleep; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Terror; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Vertigo; import com.shatteredpixel.shatteredpixeldungeon.effects.Pushing; import com.shatteredpixel.shatteredpixeldungeon.effects.particles.ShadowParticle; import com.shatteredpixel.shatteredpixeldungeon.items.keys.SkeletonKey; import com.shatteredpixel.shatteredpixeldungeon.items.scrolls.ScrollOfPsionicBlast; import com.shatteredpixel.shatteredpixeldungeon.items.weapon.enchantments.Death; import com.shatteredpixel.shatteredpixeldungeon.levels.Level; import com.shatteredpixel.shatteredpixeldungeon.mechanics.Ballistica; import com.shatteredpixel.shatteredpixeldungeon.messages.Messages; import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene; import com.shatteredpixel.shatteredpixeldungeon.sprites.BurningFistSprite; import com.shatteredpixel.shatteredpixeldungeon.sprites.CharSprite; import com.shatteredpixel.shatteredpixeldungeon.sprites.LarvaSprite; import com.shatteredpixel.shatteredpixeldungeon.sprites.RottingFistSprite; import com.shatteredpixel.shatteredpixeldungeon.sprites.YogSprite; import com.shatteredpixel.shatteredpixeldungeon.ui.BossHealthBar; import com.shatteredpixel.shatteredpixeldungeon.utils.GLog; import com.shatteredpixel.shatteredpixeldungeon.utils.Utils; import com.watabou.utils.Bundle; import com.watabou.utils.Random; public class Yog extends Mob { { spriteClass = YogSprite.class; HP = HT = 300; EXP = 50; state = PASSIVE; properties.add(Property.BOSS); properties.add(Property.IMMOVABLE); properties.add(Property.DEMONIC); } public Yog() { super(); } public void spawnFists() { RottingFist fist1 = new RottingFist(); BurningFist fist2 = new BurningFist(); do { fist1.pos = pos + Level.NEIGHBOURS8[Random.Int( 8 )]; fist2.pos = pos + Level.NEIGHBOURS8[Random.Int( 8 )]; } while (!Level.passable[fist1.pos] || !Level.passable[fist2.pos] || fist1.pos == fist2.pos); GameScene.add( fist1 ); GameScene.add( fist2 ); notice(); } @Override protected boolean act() { //heals 1 health per turn HP = Math.min( HT, HP+1 ); return super.act(); } @Override public void damage( int dmg, Object src ) { HashSet fists = new HashSet<>(); for (Mob mob : Dungeon.level.mobs) if (mob instanceof RottingFist || mob instanceof BurningFist) fists.add( mob ); for (Mob fist : fists) fist.beckon( pos ); dmg >>= fists.size(); super.damage( dmg, src ); LockedFloor lock = Dungeon.hero.buff(LockedFloor.class); if (lock != null) lock.addTime(dmg*0.5f); } @Override public int defenseProc( Char enemy, int damage ) { ArrayList spawnPoints = new ArrayList<>(); for (int i=0; i < Level.NEIGHBOURS8.length; i++) { int p = pos + Level.NEIGHBOURS8[i]; if (Actor.findChar( p ) == null && (Level.passable[p] || Level.avoid[p])) { spawnPoints.add( p ); } } if (spawnPoints.size() > 0) { Larva larva = new Larva(); larva.pos = Random.element( spawnPoints ); GameScene.add( larva ); Actor.addDelayed( new Pushing( larva, pos, larva.pos ), -1 ); } for (Mob mob : Dungeon.level.mobs) { if (mob instanceof BurningFist || mob instanceof RottingFist || mob instanceof Larva) { mob.aggro( enemy ); } } return super.defenseProc(enemy, damage); } @Override public void beckon( int cell ) { } @SuppressWarnings("unchecked") @Override public void die( Object cause ) { for (Mob mob : (Iterable)Dungeon.level.mobs.clone()) { if (mob instanceof BurningFist || mob instanceof RottingFist) { mob.die( cause ); } } GameScene.bossSlain(); Dungeon.level.drop( new SkeletonKey( Dungeon.depth ), pos ).sprite.drop(); super.die( cause ); yell( Messages.get(this, "defeated") ); } @Override public void notice() { super.notice(); BossHealthBar.assignBoss(this); yell( Messages.get(this, "notice") ); } private static final HashSet> IMMUNITIES = new HashSet<>(); static { IMMUNITIES.add( Death.class ); IMMUNITIES.add( Terror.class ); IMMUNITIES.add( Amok.class ); IMMUNITIES.add( Charm.class ); IMMUNITIES.add( Sleep.class ); IMMUNITIES.add( Burning.class ); IMMUNITIES.add( ToxicGas.class ); IMMUNITIES.add( ScrollOfPsionicBlast.class ); IMMUNITIES.add( Vertigo.class ); } @Override public HashSet> immunities() { return IMMUNITIES; } @Override public void restoreFromBundle(Bundle bundle) { super.restoreFromBundle(bundle); BossHealthBar.assignBoss(this); } public static class RottingFist extends Mob { private static final int REGENERATION = 4; { spriteClass = RottingFistSprite.class; HP = HT = 300; defenseSkill = 25; EXP = 0; state = WANDERING; properties.add(Property.BOSS); properties.add(Property.DEMONIC); } @Override public int attackSkill( Char target ) { return 36; } @Override public int damageRoll() { return Random.NormalIntRange( 24, 36 ); } @Override public int dr() { return 15; } @Override public int attackProc( Char enemy, int damage ) { if (Random.Int( 3 ) == 0) { Buff.affect( enemy, Ooze.class ); enemy.sprite.burst( 0xFF000000, 5 ); } return damage; } @Override public boolean act() { if (Level.water[pos] && HP < HT) { sprite.emitter().burst( ShadowParticle.UP, 2 ); HP += REGENERATION; } return super.act(); } @Override public void damage(int dmg, Object src) { super.damage(dmg, src); LockedFloor lock = Dungeon.hero.buff(LockedFloor.class); if (lock != null) lock.addTime(dmg*0.5f); } private static final HashSet> RESISTANCES = new HashSet<>(); static { RESISTANCES.add( ToxicGas.class ); RESISTANCES.add( Death.class ); RESISTANCES.add( ScrollOfPsionicBlast.class ); } @Override public HashSet> resistances() { return RESISTANCES; } private static final HashSet> IMMUNITIES = new HashSet<>(); static { IMMUNITIES.add( Amok.class ); IMMUNITIES.add( Sleep.class ); IMMUNITIES.add( Terror.class ); IMMUNITIES.add( Poison.class ); IMMUNITIES.add( Vertigo.class ); } @Override public HashSet> immunities() { return IMMUNITIES; } } public static class BurningFist extends Mob { { name = "burning fist"; spriteClass = BurningFistSprite.class; HP = HT = 200; defenseSkill = 25; EXP = 0; state = WANDERING; properties.add(Property.BOSS); properties.add(Property.DEMONIC); } @Override public int attackSkill( Char target ) { return 36; } @Override public int damageRoll() { return Random.NormalIntRange( 20, 32 ); } @Override public int dr() { return 15; } @Override protected boolean canAttack( Char enemy ) { return new Ballistica( pos, enemy.pos, Ballistica.MAGIC_BOLT).collisionPos == enemy.pos; } @Override public boolean attack( Char enemy ) { if (!Level.adjacent( pos, enemy.pos )) { spend( attackDelay() ); if (hit( this, enemy, true )) { int dmg = damageRoll(); enemy.damage( dmg, this ); enemy.sprite.bloodBurstA( sprite.center(), dmg ); enemy.sprite.flash(); if (!enemy.isAlive() && enemy == Dungeon.hero) { Dungeon.fail( Utils.format( ResultDescriptions.UNIQUE, name ) ); GLog.n( Messages.get(Char.class, "kill", name) ); } return true; } else { enemy.sprite.showStatus( CharSprite.NEUTRAL, enemy.defenseVerb() ); return false; } } else { return super.attack( enemy ); } } @Override public boolean act() { for (int i=0; i < Level.NEIGHBOURS9.length; i++) { GameScene.add( Blob.seed( pos + Level.NEIGHBOURS9[i], 2, Fire.class ) ); } return super.act(); } @Override public void damage(int dmg, Object src) { super.damage(dmg, src); LockedFloor lock = Dungeon.hero.buff(LockedFloor.class); if (lock != null) lock.addTime(dmg*0.5f); } private static final HashSet> RESISTANCES = new HashSet<>(); static { RESISTANCES.add( ToxicGas.class ); RESISTANCES.add( Death.class ); } @Override public HashSet> resistances() { return RESISTANCES; } private static final HashSet> IMMUNITIES = new HashSet<>(); static { IMMUNITIES.add( Amok.class ); IMMUNITIES.add( Sleep.class ); IMMUNITIES.add( Terror.class ); IMMUNITIES.add( Burning.class ); IMMUNITIES.add( ScrollOfPsionicBlast.class ); IMMUNITIES.add( Vertigo.class ); } @Override public HashSet> immunities() { return IMMUNITIES; } } public static class Larva extends Mob { { spriteClass = LarvaSprite.class; HP = HT = 25; defenseSkill = 20; EXP = 0; state = HUNTING; properties.add(Property.DEMONIC); } @Override public int attackSkill( Char target ) { return 30; } @Override public int damageRoll() { return Random.NormalIntRange( 15, 20 ); } @Override public int dr() { return 8; } } }