public class Assets {

	public static final String ARCS_BG		= "arcs1.png";
	public static final String ARCS_FG		= "arcs2.png";
	public static final String DASHBOARD	= "dashboard.png";
	
	public static final String BANNERS	= "banners.png";
	public static final String BADGES	= "badges.png";
	public static final String AMULET	= "amulet.png";
	
	public static final String CHROME	= "chrome.png";
	public static final String ICONS	= "icons.png";
	public static final String STATUS	= "status_pane.png";
	public static final String HP_BAR	= "hp_bar.png";
	public static final String XP_BAR	= "exp_bar.png";
	public static final String TOOLBAR	= "toolbar.png";
	
	public static final String WARRIOR	= "warrior.png";
	public static final String MAGE		= "mage.png";
	public static final String ROGUE	= "rogue.png";
	public static final String HUNTRESS	= "ranger.png";
	public static final String AVATARS	= "avatars.png";
    public static final String PET		= "pet.png";
	
	public static final String SURFACE	= "surface.png";
	
	public static final String FIREBALL		= "fireball.png";
	public static final String SPECKS		= "specks.png";
	public static final String EFFECTS		= "effects.png";
	
	public static final String RAT		= "rat.png";
	public static final String GNOLL	= "gnoll.png";
	public static final String CRAB		= "crab.png";
	public static final String GOO		= "goo.png";
	public static final String SWARM	= "swarm.png";
	public static final String SKELETON	= "skeleton.png";
	public static final String SHAMAN	= "shaman.png";
	public static final String THIEF	= "thief.png";
	public static final String TENGU	= "tengu.png";
	public static final String SHEEP	= "sheep.png";
	public static final String KEEPER	= "shopkeeper.png";
	public static final String BAT		= "bat.png";
	public static final String BRUTE	= "brute.png";
	public static final String SPINNER	= "spinner.png";
	public static final String DM300	= "dm300.png";
	public static final String WRAITH	= "wraith.png";
	public static final String ELEMENTAL= "elemental.png";
	public static final String MONK		= "monk.png";
	public static final String WARLOCK	= "warlock.png";
	public static final String GOLEM	= "golem.png";
	public static final String UNDEAD	= "undead.png";
	public static final String KING		= "king.png";
	public static final String STATUE	= "statue.png";
	public static final String PIRANHA	= "piranha.png";
	public static final String EYE		= "eye.png";
	public static final String SUCCUBUS	= "succubus.png";
	public static final String SCORPIO	= "scorpio.png";
	public static final String ROTTING	= "rotting_fist.png";
	public static final String BURNING	= "burning_fist.png";
	public static final String YOG		= "yog.png";
	public static final String LARVA	= "larva.png";
	public static final String GHOST	= "ghost.png";
	public static final String MAKER	= "wandmaker.png";
	public static final String TROLL	= "blacksmith.png";
	public static final String IMP		= "demon.png";
	public static final String RATKING	= "ratking.png";
	
	public static final String ITEMS	= "items.png";
	public static final String PLANTS	= "plants.png";
	
	public static final String TILES_SEWERS	= "tiles0.png";
	public static final String TILES_PRISON	= "tiles1.png";
	public static final String TILES_CAVES	= "tiles2.png";
	public static final String TILES_CITY	= "tiles3.png";
	public static final String TILES_HALLS	= "tiles4.png";
	
	public static final String WATER_SEWERS	= "water0.png";
	public static final String WATER_PRISON	= "water1.png";
	public static final String WATER_CAVES	= "water2.png";
	public static final String WATER_CITY	= "water3.png";
	public static final String WATER_HALLS	= "water4.png";
	
	public static final String BUFFS_SMALL	= "buffs.png";
	public static final String BUFFS_LARGE	= "large_buffs.png";
	public static final String SPELL_ICONS	= "spell_icons.png";
	
	public static final String FONTS1X	= "font1x.png";
	public static final String FONTS15X	= "font15x.png";
	public static final String FONTS2X	= "font2x.png";
	public static final String FONTS25X	= "font25x.png";
	public static final String FONTS3X	= "font3x.png";
	
	public static final String THEME	= "theme.mp3";
	public static final String TUNE		= "game.mp3";
	public static final String HAPPY	= "surface.mp3";
	
	public static final String SND_CLICK	= "snd_click.mp3";
	public static final String SND_BADGE	= "snd_badge.mp3";
	public static final String SND_GOLD		= "snd_gold.mp3";
	
	public static final String SND_OPEN		= "snd_door_open.mp3";
	public static final String SND_UNLOCK	= "snd_unlock.mp3";
	public static final String SND_ITEM		= "snd_item.mp3";
	public static final String SND_DEWDROP	= "snd_dewdrop.mp3";
	public static final String SND_HIT		= "snd_hit.mp3";
	public static final String SND_MISS		= "snd_miss.mp3";
	public static final String SND_STEP		= "snd_step.mp3";
	public static final String SND_WATER	= "snd_water.mp3";
	public static final String SND_DESCEND	= "snd_descend.mp3";
	public static final String SND_EAT		= "snd_eat.mp3";
	public static final String SND_READ		= "snd_read.mp3";
	public static final String SND_LULLABY	= "snd_lullaby.mp3";
	public static final String SND_DRINK	= "snd_drink.mp3";
	public static final String SND_SHATTER	= "snd_shatter.mp3";
	public static final String SND_ZAP		= "snd_zap.mp3";
	public static final String SND_LIGHTNING= "snd_lightning.mp3";
	public static final String SND_LEVELUP	= "snd_levelup.mp3";
	public static final String SND_DEATH	= "snd_death.mp3";
	public static final String SND_CHALLENGE= "snd_challenge.mp3";
	public static final String SND_CURSED	= "snd_cursed.mp3";
	public static final String SND_TRAP		= "snd_trap.mp3";
	public static final String SND_EVOKE	= "snd_evoke.mp3";
	public static final String SND_TOMB		= "snd_tomb.mp3";
	public static final String SND_ALERT	= "snd_alert.mp3";
	public static final String SND_MELD		= "snd_meld.mp3";
	public static final String SND_BOSS		= "snd_boss.mp3";
	public static final String SND_BLAST	= "snd_blast.mp3";
	public static final String SND_PLANT	= "snd_plant.mp3";
	public static final String SND_RAY		= "snd_ray.mp3";
	public static final String SND_BEACON	= "snd_beacon.mp3";
	public static final String SND_TELEPORT	= "snd_teleport.mp3";
	public static final String SND_CHARMS	= "snd_charms.mp3";
	public static final String SND_MASTERY	= "snd_mastery.mp3";
	public static final String SND_PUFF		= "snd_puff.mp3";
	public static final String SND_ROCKS	= "snd_rocks.mp3";
	public static final String SND_BURNING	= "snd_burning.mp3";
	public static final String SND_FALLING	= "snd_falling.mp3";
	public static final String SND_GHOST	= "snd_ghost.mp3";
	public static final String SND_SECRET	= "snd_secret.mp3";
	public static final String SND_BONES	= "snd_bones.mp3";
} public class ShatteredPixelDungeon extends Game {
	
	public ShatteredPixelDungeon() {
		super( TitleScene.class );
		/*
        // 1.7.2
        com.watabou.utils.Bundle.addAlias(
                com.shatteredpixel.shatteredpixeldungeon.plants.Dreamweed.class,
                "com.shatteredpixel.shatteredpixeldungeon.plants.Blindweed" );
        com.watabou.utils.Bundle.addAlias(
                com.shatteredpixel.shatteredpixeldungeon.plants.Dreamweed.Seed.class,
                "com.shatteredpixel.shatteredpixeldungeon.plants.Blindweed$Seed" );
        */
	}
	
	@SuppressWarnings("deprecation")
	@Override
	protected void onCreate( Bundle savedInstanceState ) {
		super.onCreate(savedInstanceState);
		
	/*	if (android.os.Build.VERSION.SDK_INT >= 19) {
			getWindow().getDecorView().setSystemUiVisibility( 
				View.SYSTEM_UI_FLAG_LAYOUT_STABLE | 
				View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | 
				View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | 
				View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | 
				View.SYSTEM_UI_FLAG_FULLSCREEN | 
				View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY );
		}*/

        updateImmersiveMode();

        DisplayMetrics metrics = new DisplayMetrics();
        instance.getWindowManager().getDefaultDisplay().getMetrics( metrics );
        boolean landscape = metrics.widthPixels > metrics.heightPixels;
		
		if (Preferences.INSTANCE.getBoolean( Preferences.KEY_LANDSCAPE, false ) != landscape) {
			landscape( !landscape );
		}
		
		Music.INSTANCE.enable( music() );
		Sample.INSTANCE.enable( soundFx() );
	}

    @Override
    public void onWindowFocusChanged( boolean hasFocus ) {

        super.onWindowFocusChanged( hasFocus );

        if (hasFocus) {
            updateImmersiveMode();
        }
    }

    public static void switchNoFade( Class<? extends PixelScene> c ) {
        PixelScene.noFade = true;
        switchScene( c );
    }
	
	/*
	 * ---> Prefernces
	 */
	
	public static void landscape( boolean value ) {
		Game.instance.setRequestedOrientation( value ?
			ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE :
			ActivityInfo.SCREEN_ORIENTATION_PORTRAIT );
		Preferences.INSTANCE.put( Preferences.KEY_LANDSCAPE, value );
	}
	
	public static boolean landscape() {
		return width > height;
	}
	
	public static void scaleUp( boolean value ) {
		Preferences.INSTANCE.put( Preferences.KEY_SCALE_UP, value );
		switchScene( TitleScene.class );
	}

	public static void softify( boolean value ) {
		Preferences.INSTANCE.put( Preferences.KEY_SOFTIFY, value );
	}
	
	public static boolean softify() {
		return Preferences.INSTANCE.getBoolean( Preferences.KEY_SOFTIFY, true );
	}
	
    // *** IMMERSIVE MODE ****

    private static boolean immersiveModeChanged = false;

    @SuppressLint("NewApi")
    public static void immerse( boolean value ) {
        Preferences.INSTANCE.put( Preferences.KEY_IMMERSIVE, value );

        instance.runOnUiThread( new Runnable() {
            @Override
            public void run() {
                updateImmersiveMode();
                immersiveModeChanged = true;
            }
        } );
    }

    @Override
    public void onSurfaceChanged( GL10 gl, int width, int height ) {
        super.onSurfaceChanged( gl, width, height );

        if (immersiveModeChanged) {
            requestedReset = true;
            immersiveModeChanged = false;
        }
    }

    @SuppressLint("NewApi")
    public static void updateImmersiveMode() {
        if (android.os.Build.VERSION.SDK_INT >= 19) {
            instance.getWindow().getDecorView().setSystemUiVisibility(
                    immersed() ?
                            
                                    View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                            :
                            0 );
        }
    }
/*
 *                             View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
                                    View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
                                    View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
                                    View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
                                    View.SYSTEM_UI_FLAG_FULLSCREEN |
                                    View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
 */
    public static boolean immersed() {
        return Preferences.INSTANCE.getBoolean( Preferences.KEY_IMMERSIVE, false );
    }

    // *****************************
	
	public static boolean scaleUp() {
		return Preferences.INSTANCE.getBoolean( Preferences.KEY_SCALE_UP, true );
	}

	public static void zoom( int value ) {
		Preferences.INSTANCE.put( Preferences.KEY_ZOOM, value );
	}
	
	public static int zoom() {
		return Preferences.INSTANCE.getInt( Preferences.KEY_ZOOM, 0 );
	}
	
	public static void music( boolean value ) {
		Music.INSTANCE.enable( value );
		Preferences.INSTANCE.put( Preferences.KEY_MUSIC, value );
	}
	
	public static boolean music() {
		return Preferences.INSTANCE.getBoolean( Preferences.KEY_MUSIC, false );
	}
	
	public static void soundFx( boolean value ) {
		Sample.INSTANCE.enable( value );
		Preferences.INSTANCE.put( Preferences.KEY_SOUND_FX, value );
	}
	
	public static boolean soundFx() {
		return Preferences.INSTANCE.getBoolean( Preferences.KEY_SOUND_FX, false );
	}
	
	public static void brightness( boolean value ) {
		Preferences.INSTANCE.put( Preferences.KEY_BRIGHTNESS, value );
		if (scene() instanceof GameScene) {
			((GameScene)scene()).brightness( value );
		}
	}
	
	public static boolean brightness() {
		return Preferences.INSTANCE.getBoolean( Preferences.KEY_BRIGHTNESS, false );
	}
	
	public static void lastClass( int value ) {
		Preferences.INSTANCE.put( Preferences.KEY_LAST_CLASS, value );
	}
	
	public static int lastClass() {
		return Preferences.INSTANCE.getInt( Preferences.KEY_LAST_CLASS, 0 );
	}

    public static void challenges( int value ) {
        Preferences.INSTANCE.put( Preferences.KEY_CHALLENGES, value );
    }

    public static int challenges() {
        return Preferences.INSTANCE.getInt( Preferences.KEY_CHALLENGES, 0 );
    }

	public static void quickSlots( int value ){ Preferences.INSTANCE.put( Preferences.KEY_QUICKSLOTS, value ); }

	public static int quickSlots(){ return Preferences.INSTANCE.getInt( Preferences.KEY_QUICKSLOTS, 1); }
	
	public static void intro( boolean value ) {
		Preferences.INSTANCE.put( Preferences.KEY_INTRO, value );
	}
	
	public static boolean intro() {
		return Preferences.INSTANCE.getBoolean( Preferences.KEY_INTRO, true );
	}

    public static void version( int value)  {
        Preferences.INSTANCE.put( Preferences.KEY_VERSION, value );
    }

    public static int version() {
        return Preferences.INSTANCE.getInt( Preferences.KEY_VERSION, 0 );
    }
	
	/*
	 * <--- Preferences
	 */
	
	public static void reportException( Exception e ) {
		Log.e( "PD", Log.getStackTraceString( e ) ); 
	}
} public class Flare extends Visual {
	
	private float duration = 0;
	private float lifespan;
	
	private boolean lightMode = true;
	
	private SmartTexture texture;
	
	private FloatBuffer vertices;
	private ShortBuffer indices;
	
	private int nRays;
	
	@SuppressLint("FloatMath")
	public Flare( int nRays, float radius ) {
		
		super( 0, 0, 0, 0 );

        // FIXME
		// Texture is incorrectly created every time we need
		// to show the effect, it must be refactored
		
		int gradient[] = {0xFFFFFFFF, 0x00FFFFFF};
		texture = new Gradient( gradient );
		
		this.nRays = nRays;
		
		angle = 45;
		angularSpeed = 180;
		
		vertices = ByteBuffer.
			allocateDirect( (nRays * 2 + 1) * 4 * (Float.SIZE / 8) ).
			order( ByteOrder.nativeOrder() ).
			asFloatBuffer();
		
		indices = ByteBuffer.
			allocateDirect( nRays * 3 * Short.SIZE / 8 ).
			order( ByteOrder.nativeOrder() ).
			asShortBuffer();
		
		float v[] = new float[4];
		
		v[0] = 0;
		v[1] = 0;
		v[2] = 0.25f;
		v[3] = 0;
		vertices.put( v );
		
		v[2] = 0.75f;
		v[3] = 0;
		
		for (int i=0; i < nRays; i++) {
			
			float a = i * 3.1415926f * 2 / nRays;
			v[0] = FloatMath.cos( a ) * radius;
			v[1] = FloatMath.sin( a ) * radius;
			vertices.put( v );
			
			a += 3.1415926f * 2 / nRays / 2;
			v[0] = FloatMath.cos( a ) * radius;
			v[1] = FloatMath.sin( a ) * radius;
			vertices.put( v );
			
			indices.put( (short)0 );
			indices.put( (short)(1 + i * 2) );
			indices.put( (short)(2 + i * 2) );
		}
		
		indices.position( 0 );
	}
	
	public Flare color( int color, boolean lightMode ) {
		this.lightMode = lightMode;
		hardlight( color );
		
		return this;
	}
	
	public Flare show( Visual visual, float duration ) {
		point( visual.center() );
		visual.parent.addToBack( this );
		
		lifespan = this.duration = duration;
		
		return this;
	}
	
	@Override
	public void update() {
		super.update();
		
		if (duration > 0) {
			if ((lifespan -= Game.elapsed) > 0) {
				
				float p = 1 - lifespan / duration;	// 0 -> 1
				p =  p < 0.25f ? p * 4 : (1 - p) * 1.333f;
				scale.set( p );
				alpha( p );
				
			} else {
				killAndErase();
			}
		}
	}
	
	@Override
	public void draw() {
		
		super.draw();
		
		if (lightMode) {
			GLES20.glBlendFunc( GL10.GL_SRC_ALPHA, GL10.GL_ONE );
			drawRays();
			GLES20.glBlendFunc( GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA );
		} else {
			drawRays();
		}
	}
	
	private void drawRays() {
		
		NoosaScript script = NoosaScript.get();
		
		texture.bind();
		
		script.uModel.valueM4( matrix );
		script.lighting( 
			rm, gm, bm, am, 
			ra, ga, ba, aa );
		
		script.camera( camera );
		script.drawElements( vertices, indices, nRays * 3 );
	}
} public class Heap implements Bundlable {

	private static final int SEEDS_TO_POTION = 3;
	
	public enum Type {
		HEAP, 
		FOR_SALE, 
		CHEST, 
		LOCKED_CHEST, 
		CRYSTAL_CHEST,
		TOMB, 
		SKELETON,
        REMAINS
	}
	public Type type = Type.HEAP;
	
	public int pos = 0;
	
	public ItemSprite sprite;
	
	protected LinkedList<Item> items = new LinkedList<Item>();
	
	public int image() {
		switch (type) {
		case HEAP:
		case FOR_SALE:
			return size() > 0 ? items.peek().image() : 0;
		case CHEST:
			return ItemSpriteSheet.CHEST;
		case LOCKED_CHEST:
			return ItemSpriteSheet.LOCKED_CHEST;
		case CRYSTAL_CHEST:
			return ItemSpriteSheet.CRYSTAL_CHEST;
		case TOMB:
			return ItemSpriteSheet.TOMB;
		case SKELETON:
			return ItemSpriteSheet.BONES;
        case REMAINS:
            return ItemSpriteSheet.REMAINS;
		default:
			return 0;
		}
	}
	
	public ItemSprite.Glowing glowing() {
		return (type == Type.HEAP || type == Type.FOR_SALE) && items.size() > 0 ? items.peek().glowing() : null;
	}
	
	public void open( Hero hero ) {
		switch (type) {
		case TOMB:
			Wraith.spawnAround( hero.pos );
			break;
		case SKELETON:
        case REMAINS:
			CellEmitter.center( pos ).start( Speck.factory( Speck.RATTLE ), 0.1f, 3 );
			for (Item item : items) {
				if (item.cursed) {
					if (Wraith.spawnAt( pos ) == null) {
						hero.sprite.emitter().burst( ShadowParticle.CURSE, 6 );
						hero.damage( hero.HP / 2, this );
					}
					Sample.INSTANCE.play( Assets.SND_CURSED );
					break;
				}
			}
			break;
		default:
		}
		
		type = Type.HEAP;
		sprite.link();
		sprite.drop();
	}
	
	public int size() {
		return items.size();
	}
	
	public Item pickUp() {
		
		Item item = items.removeFirst();
		if (items.isEmpty()) {
			destroy();
		} else if (sprite != null) {
			sprite.view( image(), glowing() );
		}
		
		return item;
	}
	
	public Item peek() {
		return items.peek();
	}
	
	public void drop( Item item ) {
		
		if (item.stackable) {
			
			for (Item i : items) {
				if (i.isSimilar( item )) {
					i.quantity += item.quantity;
					item = i;
					break;
				}
			}
			items.remove( item );
			
		}
		
		if (item instanceof Dewdrop) {
			items.add( item );
		} else {
			items.addFirst( item );
		}
		
		if (sprite != null) {
			sprite.view( image(), glowing() );
		}
	}
	
	public void replace( Item a, Item b ) {
		int index = items.indexOf( a );
		if (index != -1) {
			items.remove( index );
			items.add( index, b );
		}
	}
	
	public void burn() {
		
		if (type != Type.HEAP) {
			return;
		}
		
		boolean burnt = false;
		boolean evaporated = false;
		
		for (Item item : items.toArray( new Item[0] )) {
			if (item instanceof Scroll) {
				items.remove( item );
				burnt = true;
			} else if (item instanceof Dewdrop) {
				items.remove( item );
				evaporated = true;
			} else if (item instanceof MysteryMeat) {
				replace( item, ChargrilledMeat.cook( (MysteryMeat)item ) );
				burnt = true;
			}
		}
		
		if (burnt || evaporated) {
			
			if (Dungeon.visible[pos]) {
				if (burnt) {
					burnFX( pos );
				} else {
					evaporateFX( pos );
				}
			}
			
			if (isEmpty()) {
				destroy();
			} else if (sprite != null) {
				sprite.view( image(), glowing() );
			}
			
		}
	}
	
	public void freeze() {
		
		if (type != Type.HEAP) {
			return;
		}
		
		boolean frozen = false;
		for (Item item : items.toArray( new Item[0] )) {
			if (item instanceof MysteryMeat) {
				replace( item, FrozenCarpaccio.cook( (MysteryMeat)item ) );
				frozen = true;
			}
		}
		
		if (frozen) {
			if (isEmpty()) {
				destroy();
			} else if (sprite != null) {
				sprite.view( image(), glowing() );
			}	
		}
	}
	
	public Item transmute() {
		
		CellEmitter.get( pos ).burst( Speck.factory( Speck.BUBBLE ), 3 );
		Splash.at( pos, 0xFFFFFF, 3 );
		
		float chances[] = new float[items.size()];
		int count = 0;


        if (items.size() == 2 && items.get(0) instanceof Seed && items.get(1) instanceof Blandfruit ) {

            Sample.INSTANCE.play( Assets.SND_PUFF );
            CellEmitter.center( pos ).burst( Speck.factory( Speck.EVOKE ), 3 );

            Blandfruit result = new Blandfruit();
            result.cook((Seed)items.get(0));

            destroy();

            return result;

        }
		
		int index = 0;
		for (Item item : items) {
			if (item instanceof Seed) {
				count += item.quantity;
				chances[index++] = item.quantity;
			}  else{
				count = 0;
				break;
			}
		}

		//alchemists toolkit gives a chance to cook a potion in two or even one seeds
		AlchemistsToolkit.alchemy alchemy = Dungeon.hero.buff(AlchemistsToolkit.alchemy.class);
		int bonus = alchemy != null ? alchemy.level() : -1;

		if (bonus != -1 ? alchemy.tryCook(count) : count >= SEEDS_TO_POTION) {

			CellEmitter.get( pos ).burst( Speck.factory( Speck.WOOL ), 6 );
			Sample.INSTANCE.play( Assets.SND_PUFF );

            Item potion;

			if (Random.Int( count + bonus ) == 0) {

				CellEmitter.center( pos ).burst( Speck.factory( Speck.EVOKE ), 3 );

				destroy();

				Statistics.potionsCooked++;
				Badges.validatePotionsCooked();

                potion = Generator.random( Generator.Category.POTION );

			} else {

				Seed proto = (Seed)items.get( Random.chances( chances ) );
				Class<? extends Item> itemClass = proto.alchemyClass;

				destroy();

				Statistics.potionsCooked++;
				Badges.validatePotionsCooked();

				if (itemClass == null) {
                    potion =  Generator.random( Generator.Category.POTION );
				} else {
					try {
                        potion =  itemClass.newInstance();
					} catch (Exception e) {
						return null;
					}
				}
			}

			//not a buff per-se, meant to cancel out higher potion accuracy when ppl are farming for potions of exp.
			if (bonus > 0)
				if (Random.Int(1000/bonus) == 0)
					return new PotionOfExperience();

            while (potion instanceof PotionOfHealing && Random.Int(15) - Dungeon.limitedDrops.cookingHP.count >= 0)
                potion = Generator.random( Generator.Category.POTION );

            if (potion instanceof PotionOfHealing)
                Dungeon.limitedDrops.cookingHP.count++;

            return potion;

		} else {
			return null;
		}
	}
	
	public static void burnFX( int pos ) {
		CellEmitter.get( pos ).burst( ElmoParticle.FACTORY, 6 );
		Sample.INSTANCE.play( Assets.SND_BURNING );
	}
	
	public static void evaporateFX( int pos ) {
		CellEmitter.get( pos ).burst( Speck.factory( Speck.STEAM ), 5 );
	}
	
	public boolean isEmpty() {
		return items == null || items.size() == 0;
	}
	
	public void destroy() {
		Dungeon.level.heaps.remove( this.pos );
		if (sprite != null) {
			sprite.kill();
		}
		items.clear();
		items = null;
	}

	private static final String POS		= "pos";
	private static final String TYPE	= "type";
	private static final String ITEMS	= "items";
	
	@SuppressWarnings("unchecked")
	@Override
	public void restoreFromBundle( Bundle bundle ) {
		pos = bundle.getInt( POS );
		type = Type.valueOf( bundle.getString( TYPE ) );
        items = new LinkedList<Item>( (Collection<Item>) ((Collection<?>) bundle.getCollection( ITEMS )) );
        items.removeAll(Collections.singleton(null));
	}

	@Override
	public void storeInBundle( Bundle bundle ) {
		bundle.put( POS, pos );
		bundle.put( TYPE, type.toString() );
		bundle.put( ITEMS, items );
	}
	
} public enum Type {
	HEAP, 
	FOR_SALE, 
	CHEST, 
	LOCKED_CHEST, 
	CRYSTAL_CHEST,
	TOMB, 
	SKELETON,
       REMAINS
} public class Key extends Item {

	public static final float TIME_TO_UNLOCK = 1f;
	
	{
		stackable = true;
	}
	
	public int depth;
	
	@Override
	public boolean isSimilar( Item item ) {
		return item.getClass() == getClass() && ((Key)item).depth == depth;
	}
	
	private static final String DEPTH = "depth";
	
	@Override
	public void storeInBundle( Bundle bundle ) {
		super.storeInBundle( bundle );
		bundle.put( DEPTH, depth );
	}
	
	@Override
	public void restoreFromBundle( Bundle bundle ) {
		super.restoreFromBundle( bundle );
		depth = bundle.getInt( DEPTH );
	}
	
	@Override
	public boolean isUpgradable() {
		return false;
	}
	
	@Override
	public boolean isIdentified() {
		return true;
	}

} public class ScrollOfMagicMapping extends Scroll {

	private static final String TXT_LAYOUT = "You are now aware of the level layout.";
	
	{
		name = "Scroll of Magic Mapping";
	}
	
	@Override
	protected void doRead() {
		
		int length = Level.LENGTH;
		int[] map = Dungeon.level.map;
		boolean[] mapped = Dungeon.level.mapped;
		boolean[] discoverable = Level.discoverable;
		
		boolean noticed = false;
		
		for (int i=0; i < length; i++) {
			
			int terr = map[i];
			
			if (discoverable[i]) {
				
				mapped[i] = true;
				if ((Terrain.flags[terr] & Terrain.SECRET) != 0) {
					
					Level.set( i, Terrain.discover( terr ) );						
					GameScene.updateMap( i );
					
					if (Dungeon.visible[i]) {
						GameScene.discoverTile( i, terr );
						discover( i );
						
						noticed = true;
					}
				}
			}
		}
		Dungeon.observe();
		
		GLog.i( TXT_LAYOUT );
		if (noticed) {
			Sample.INSTANCE.play( Assets.SND_SECRET );
		}
		
		SpellSprite.show( curUser, SpellSprite.MAP );
		Sample.INSTANCE.play( Assets.SND_READ );
		Invisibility.dispel();
		
		setKnown();
		
		curUser.spendAndNext( TIME_TO_READ );
	}
	
	@Override
	public String desc() {
		return
			"When this scroll is read, an image of crystal clarity will be etched into your memory, " +
			"alerting you to the precise layout of the level and revealing all hidden secrets. " +
			"The locations of items and creatures will remain unknown.";
	}
	
	@Override
	public int price() {
		return isKnown() ? 25 * quantity : super.price();
	}
	
	public static void discover( int cell ) {
		CellEmitter.get( cell ).start( Speck.factory( Speck.DISCOVER ), 0.1f, 4 );
	}
} public class MeleeWeapon extends Weapon {
	
	private int tier;
	
	public MeleeWeapon( int tier, float acu, float dly ) {
		super();
		
		this.tier = tier;
		
		ACU = acu;
		DLY = dly;
		
		STR = typicalSTR();
		
		MIN = min();
		MAX = max();
	}
	
	private int min() {
		return tier;
	}
	
	private int max() {
		return (int)((tier * tier - tier + 10) / ACU * DLY);
	}
	
	@Override
	public Item upgrade() {
		return upgrade( false );
	}
	
	public Item upgrade( boolean enchant ) {
		STR--;		
		MIN++;
		MAX += tier;
		
		return super.upgrade( enchant );
	}
	
	public Item safeUpgrade() {
		return upgrade( enchantment != null );
	}
	
	@Override
	public Item degrade() {		
		STR++;
		MIN--;
		MAX -= tier;
		return super.degrade();
	}
	
	public int typicalSTR() {
		return 8 + tier * 2;
	}
	
	@Override
	public String info() {
		
		final String p = "\n\n";
		
		StringBuilder info = new StringBuilder( desc() );
		
		String quality = levelKnown && level != 0 ? (level > 0 ? "upgraded" : "degraded") : "";
		info.append( p );
		info.append( "This " + name + " is " + Utils.indefinite( quality ) );
		info.append( " tier-" + tier + " melee weapon. " );
		
		if (levelKnown) {
			info.append( "Its average damage is " +
                Math.round((MIN + (MAX - MIN) / 2)*(imbue == Imbue.LIGHT ? 0.75f : (imbue == Imbue.HEAVY ? 1.5f : 1)))
                + " points per hit. " );
		} else {
			info.append( 
				"Its typical average damage is " + (min() + (max() - min()) / 2) + " points per hit " +
				"and usually it requires " + typicalSTR() + " points of strength. " );
			if (typicalSTR() > Dungeon.hero.STR()) {
				info.append( "Probably this weapon is too heavy for you. " );
			}
		}
		
		if (DLY != 1f) {
			info.append( "This is a rather " + (DLY < 1f ? "fast" : "slow") );
			if (ACU != 1f) {
				if ((ACU > 1f) == (DLY < 1f)) {
					info.append( " and ");
				} else {
					info.append( " but ");
				}
				info.append( ACU > 1f ? "accurate" : "inaccurate" );
			}
			info.append( " weapon. ");
		} else if (ACU != 1f) {
			info.append( "This is a rather " + (ACU > 1f ? "accurate" : "inaccurate") + " weapon. " );
        }
        switch (imbue) {
            case LIGHT:
                info.append( "It was balanced to be lighter. " );
                break;
            case HEAVY:
                info.append( "It was balanced to be heavier. " );
                break;
            case NONE:
        }
		
		if (enchantment != null) {
			info.append( "It is enchanted." );
		}
		
		if (levelKnown && Dungeon.hero.belongings.backpack.items.contains( this )) {
			if (STR > Dungeon.hero.STR()) {
				info.append( p );
				info.append( 
					"Because of your inadequate strength the accuracy and speed " +
					"of your attack with this " + name + " is decreased." );
			}
			if (STR < Dungeon.hero.STR()) {
				info.append( p );
				info.append( 
					"Because of your excess strength the damage " +
					"of your attack with this " + name + " is increased." );
			}
		}
		
		if (isEquipped( Dungeon.hero )) {
			info.append( p );
			info.append( "You hold the " + name + " at the ready" + 
				(cursed ? ", and because it is cursed, you are powerless to let go." : ".") ); 
		} else {
			if (cursedKnown && cursed) {
				info.append( p );
				info.append( "You can feel a malevolent magic lurking within " + name +"." );
			}
		}
		
		return info.toString();
	}
	
	@Override
	public int price() {
		int price = 20 * (1 << (tier - 1));
		if (enchantment != null) {
			price *= 1.5;
		}
		if (cursed && cursedKnown) {
			price /= 2;
		}
		if (levelKnown) {
			if (level > 0) {
				price *= (level + 1);
			} else if (level < 0) {
				price /= (1 - level);
			}
		}
		if (price < 1) {
			price = 1;
		}
		return price;
	}
	
	@Override
	public Item random() {
		super.random();
		
		if (Random.Int( 10 + level ) == 0) {
			enchant( Enchantment.random() );
		}
		
		return this;
	}
} public class Sungrass extends Plant {

	private static final String TXT_DESC = "Sungrass is renowned for its sap's slow but effective healing properties.";
	
	{
		image = 4;
		plantName = "Sungrass";
	}
	
	@Override
	public void activate( Char ch ) {
		super.activate( ch );
		
		if (ch != null) {
			Buff.affect( ch, Health.class ).level = ch.HT;
		}
		
		if (Dungeon.visible[pos]) {
			CellEmitter.get( pos ).start( ShaftParticle.FACTORY, 0.2f, 3 );
		}
	}
	
	@Override
	public String desc() {
		return TXT_DESC;
	}
	
	public static class Seed extends Plant.Seed {
		{
			plantName = "Sungrass";
			
			name = "seed of " + plantName;
			image = ItemSpriteSheet.SEED_SUNGRASS;
			
			plantClass = Sungrass.class;
			alchemyClass = PotionOfHealing.class;

            bones = true;
		}
		
		@Override
		public String desc() {
			return TXT_DESC;
		}
	}
	
	public static class Health extends Buff {
		
		private static final float STEP = 1f;
		
		private int pos;
        private int healCurr = 1;
        private int count = 0;
        private int level;
		
		@Override
		public boolean attachTo( Char target ) {
			pos = target.pos;
			return super.attachTo( target );
		}
		
		@Override
        public boolean act() {
            if (target.pos != pos) {
                detach();
            }
            if (count == 5) {
                if (level <= healCurr*.025*target.HT) {
                    target.HP = Math.min(target.HT, target.HP + level);
                    target.sprite.emitter().burst(Speck.factory(Speck.HEALING), 1);
                    detach();
                } else {
                    target.HP = Math.min(target.HT, target.HP+(int)(healCurr*.025*target.HT));
                    level -= (healCurr*.025*target.HT);
                    if (healCurr < 6)
                        healCurr ++;
                    target.sprite.emitter().burst(Speck.factory(Speck.HEALING), 1);
                }
                count = 1;
            } else {
                count++;
            }
            if (level <= 0)
                detach();
            spend( STEP );
            return true;
        }

        public int absorb( int damage ) {
            level -= damage;
            if (level <= 0)
                detach();
            return damage;
        }
		
		@Override
		public int icon() {
			return BuffIndicator.HEALING;
		}
		
		@Override
		public String toString() {
            return Utils.format( "Herbal Healing (%d)", level);
		}
		
		private static final String POS	= "pos";
        private static final String HEALCURR = "healCurr";
        private static final String COUNT = "count";
        private static final String LEVEL = "level";

        @Override
		public void storeInBundle( Bundle bundle ) {
			super.storeInBundle( bundle );
			bundle.put( POS, pos );
            bundle.put( HEALCURR, healCurr);
            bundle.put( COUNT, count);
            bundle.put( LEVEL, level);
		}
		
		@Override
		public void restoreFromBundle( Bundle bundle ) {
			super.restoreFromBundle( bundle );
			pos = bundle.getInt( POS );
            healCurr = bundle.getInt( HEALCURR );
            count = bundle.getInt( COUNT );
            level = bundle.getInt( LEVEL );

		}
	}
} public class InterlevelScene extends PixelScene {

	private static final float TIME_TO_FADE = 0.3f;
	
	private static final String TXT_DESCENDING	= "Descending...";
	private static final String TXT_ASCENDING	= "Ascending...";
	private static final String TXT_LOADING		= "Loading...";
	private static final String TXT_SAVING		= "Saving...";
	private static final String TXT_RESURRECTING= "Resurrecting...";
	private static final String TXT_RETURNING	= "Returning...";
	private static final String TXT_FALLING		= "Falling...";
	
	private static final String ERR_FILE_NOT_FOUND	= "Save file not found. If this error persists after restarting, " +
                                                        "it may mean this save game is corrupted. Sorry about that.";
	private static final String ERR_IO			    = "Cannot read save file. If this error persists after restarting, " +
                                                        "it may mean this save game is corrupted. Sorry about that.";
	
	public static enum Mode {
		DESCEND, ASCEND, CONTINUE, SAVE, RESURRECT, RETURN, FALL
	};
	public static Mode mode;
	
	public static int returnDepth;
	public static int returnPos;
	
	public static boolean noStory = false;
	
	public static boolean fallIntoPit;
	
	private enum Phase {
		FADE_IN, STATIC, FADE_OUT
	};
	private Phase phase;
	private float timeLeft;
	
	private BitmapText message;
	
	private Thread thread;
	private Exception error = null;
	
	@Override
	public void create() {
		super.create();

		String text = "";
		switch (mode) {
		case DESCEND:
			text = TXT_DESCENDING;
			break;
		case ASCEND:
			text = TXT_ASCENDING;
			break;
		case CONTINUE:
			text = TXT_LOADING;
			break;
		case SAVE:
			text = TXT_SAVING;
			break;
		case RESURRECT:
			text = TXT_RESURRECTING;
			break;
		case RETURN:
			text = TXT_RETURNING;
			break;
		case FALL:
			text = TXT_FALLING;
			break;
		}
		
		message = PixelScene.createText( text, 9 );
		message.measure();
		message.x = (Camera.main.width - message.width()) / 2; 
		message.y = (Camera.main.height - message.height()) / 2;
		add( message );
		
		phase = Phase.FADE_IN;
		timeLeft = TIME_TO_FADE;

		thread = new Thread() {
			@Override
			public void run() {
				
				try {
					
					Generator.reset();

					Sample.INSTANCE.load(
						Assets.SND_OPEN,
						Assets.SND_UNLOCK,
						Assets.SND_ITEM,
						Assets.SND_DEWDROP,
						Assets.SND_HIT,
						Assets.SND_MISS,
						Assets.SND_STEP,
						Assets.SND_WATER,
						Assets.SND_DESCEND,
						Assets.SND_EAT,
						Assets.SND_READ,
						Assets.SND_LULLABY,
						Assets.SND_DRINK,
						Assets.SND_SHATTER,
						Assets.SND_ZAP,
						Assets.SND_LIGHTNING,
						Assets.SND_LEVELUP,
						Assets.SND_DEATH,
						Assets.SND_CHALLENGE,
						Assets.SND_CURSED,
						Assets.SND_EVOKE,
						Assets.SND_TRAP,
						Assets.SND_TOMB,
						Assets.SND_ALERT,
						Assets.SND_MELD,
						Assets.SND_BOSS,
						Assets.SND_BLAST,
						Assets.SND_PLANT,
						Assets.SND_RAY,
						Assets.SND_BEACON,
						Assets.SND_TELEPORT,
						Assets.SND_CHARMS,
						Assets.SND_MASTERY,
						Assets.SND_PUFF,
						Assets.SND_ROCKS,
						Assets.SND_BURNING,
						Assets.SND_FALLING,
						Assets.SND_GHOST,
						Assets.SND_SECRET,
						Assets.SND_BONES );

					switch (mode) {
					case DESCEND:
						descend();
						break;
					case ASCEND:
						ascend();
						break;
					case CONTINUE:
						restore();
						break;
					case SAVE:
						restore();
						break;						
					case RESURRECT:
						resurrect();
						break;
					case RETURN:
						returnTo();
						break;
					case FALL:
						fall();
						break;
					}
					
					if ((Dungeon.depth % 5) == 0) {
						Sample.INSTANCE.load( Assets.SND_BOSS );
					}
					
				} catch (Exception e) {
					
					error = e;

				}

				if (phase == Phase.STATIC && error == null) {
					phase = Phase.FADE_OUT;
					timeLeft = TIME_TO_FADE;
				}
			}
		};
		thread.start();
	}
	
	@Override
	public void update() {
		super.update();
		
		float p = timeLeft / TIME_TO_FADE;
		
		switch (phase) {
		
		case FADE_IN:
			message.alpha( 1 - p );
			if ((timeLeft -= Game.elapsed) <= 0) {
				if (!thread.isAlive() && error == null) {
					phase = Phase.FADE_OUT;
					timeLeft = TIME_TO_FADE;
				} else {
					phase = Phase.STATIC;
				}
			}
			break;
			
		case FADE_OUT:
			message.alpha( p );

			if (mode == Mode.CONTINUE || (mode == Mode.DESCEND && Dungeon.depth == 1)) {
				Music.INSTANCE.volume( p );
			}
			if ((timeLeft -= Game.elapsed) <= 0) {
				Game.switchScene( GameScene.class );
			}
			break;
			
		case STATIC:
			if (error != null) {
				String errorMsg;
				if (error instanceof FileNotFoundException) errorMsg = ERR_FILE_NOT_FOUND;
				else if (error instanceof IOException) errorMsg = ERR_IO;

				else throw new RuntimeException("fatal error occured while moving between floors", error);

				add( new WndError( errorMsg ) {
					public void onBackPressed() {
						super.onBackPressed();
						Game.switchScene( StartScene.class );
					};
				} );
                error = null;
			}
			break;
		}
	}

	private void descend() throws IOException {

        Level level;
        ArrayList<Item> fallingItems = new ArrayList<Item>();

        Actor.fixTime();
		if (Dungeon.hero == null) {
			Dungeon.init();
			if (noStory) {
				Dungeon.chapters.add( WndStory.ID_SEWERS );
				noStory = false;
			}
		} else {
            level = Dungeon.level;

            fallingItems = level.fallingItems;
            level.fallingItems = new ArrayList<Item>();

			Dungeon.saveLevel();
		}

		if (Dungeon.depth >= Statistics.deepestFloor) {
			level = Dungeon.newLevel();
		} else {
			Dungeon.depth++;
			level = Dungeon.loadLevel( Dungeon.hero.heroClass );
		}

        for (Item item : fallingItems){
            int cell = level.randomRespawnCell();
            while (cell == -1)
                cell = level.randomRespawnCell();

            if (!(item instanceof Potion))
                level.drop(item, cell);
            else
                level.fallingPotions.add((Potion)item);
        }

		Dungeon.switchLevel( level, level.entrance );

	}
	
	private void fall() throws IOException {

        Level level = Dungeon.level;

        ArrayList<Item> fallingItems = level.fallingItems;
        level.fallingItems = new ArrayList<Item>();

        Actor.fixTime();
		Dungeon.saveLevel();

		if (Dungeon.depth >= Statistics.deepestFloor) {
			level = Dungeon.newLevel();
		} else {
			Dungeon.depth++;
			level = Dungeon.loadLevel( Dungeon.hero.heroClass );
		}

        for (Item item : fallingItems){
            int cell = level.randomRespawnCell();
            while (cell == -1)
                cell = level.randomRespawnCell();

            if (!(item instanceof Potion))
                level.drop(item, cell);
            else
                level.fallingPotions.add((Potion)item);
        }

		Dungeon.switchLevel( level, fallIntoPit ? level.pitCell() : level.randomRespawnCell() );
	}
	
	private void ascend() throws IOException {
		Actor.fixTime();
		
		Dungeon.saveLevel();
		Dungeon.depth--;
		Level level = Dungeon.loadLevel( Dungeon.hero.heroClass );
		Dungeon.switchLevel( level, level.exit );
	}
	
	private void returnTo() throws IOException {
		
		Actor.fixTime();
		
		Dungeon.saveLevel();
		Dungeon.depth = returnDepth;
		Level level = Dungeon.loadLevel( Dungeon.hero.heroClass );
		Dungeon.switchLevel( level, Level.resizingNeeded ? level.adjustPos( returnPos ) : returnPos );
	}
	
	private void restore() throws IOException {
		
		Actor.fixTime();
		
		Dungeon.loadGame( StartScene.curClass );
		if (Dungeon.depth == -1) {
			Dungeon.depth = Statistics.deepestFloor;
			Dungeon.switchLevel( Dungeon.loadLevel( StartScene.curClass ), -1 );
		} else {
			Level level = Dungeon.loadLevel( StartScene.curClass );
			Dungeon.switchLevel( level, Level.resizingNeeded ? level.adjustPos( Dungeon.hero.pos ) : Dungeon.hero.pos );
		}
	}
	
	private void resurrect() throws IOException {
		
		Actor.fixTime(); 
		
		if (Dungeon.level.locked) {
			Dungeon.hero.resurrect( Dungeon.depth );
			Dungeon.depth--;
			Level level = Dungeon.newLevel(/* true */);
			Dungeon.switchLevel( level, level.entrance );
		} else {
			Dungeon.hero.resurrect( -1 );
			Dungeon.resetLevel();
		}
	}
	
	@Override
	protected void onBackPressed() {
	}
} public class CharSprite extends MovieClip implements Tweener.Listener, MovieClip.Listener {
	
	// Color constants for floating text
	public static final int DEFAULT		= 0xFFFFFF;
	public static final int POSITIVE	= 0x00FF00;
	public static final int NEGATIVE	= 0xFF0000;
	public static final int WARNING		= 0xFF8800;
	public static final int NEUTRAL		= 0xFFFF00;
	
	private static final float MOVE_INTERVAL	= 0.1f;
	private static final float FLASH_INTERVAL	= 0.05f;	
	
	public enum State {
		BURNING, LEVITATING, INVISIBLE, PARALYSED, FROZEN, ILLUMINATED
	}
	
	protected Animation idle;
	protected Animation run;
	protected Animation attack;
	protected Animation operate;
	protected Animation zap;
	protected Animation die;
	
	protected Callback animCallback;
	
	protected Tweener motion;
	
	protected Emitter burning;
	protected Emitter levitation;
	
	protected IceBlock iceBlock;
	protected TorchHalo halo;
	
	protected EmoIcon emo;
	
	private float flashTime = 0;
	
	protected boolean sleeping = false;
	
	// Char owner
	public Char ch;

    // The sprite is currently in motion
    public boolean isMoving = false;
	
	public CharSprite() {
		super();
		listener = this;
	}
	
	public void link( Char ch ) {
		this.ch = ch;
		ch.sprite = this;
		
		place( ch.pos );
		turnTo( ch.pos, Random.Int( Level.LENGTH ) );
		
		ch.updateSpriteState();
	}
	
	public PointF worldToCamera( int cell ) {
		
		final int csize = DungeonTilemap.SIZE;
		
		return new PointF(
			((cell % Level.WIDTH) + 0.5f) * csize - width * 0.5f,
			((cell / Level.WIDTH) + 1.0f) * csize - height
		);
	}
	
	public void place( int cell ) {
		point( worldToCamera( cell ) );
	}
	
	public void showStatus( int color, String text, Object... args ) {
		if (visible) {
			if (args.length > 0) {
				text = Utils.format( text, args );
			}
			if (ch != null) {
				FloatingText.show( x + width * 0.5f, y, ch.pos, text, color );
			} else {
				FloatingText.show( x + width * 0.5f, y, text, color );
			}
		}
	}
	
	public void idle() {
		play( idle );
	}
	
	public void move( int from, int to ) {
		play( run );
		
		motion = new PosTweener( this, worldToCamera( to ), MOVE_INTERVAL );
		motion.listener = this;
		parent.add( motion );

        isMoving = true;
		
		turnTo( from , to );
		
		if (visible && Level.water[from] && !ch.flying) {
			GameScene.ripple( from );
		}

        ch.onMotionComplete();
	}
	
	public void interruptMotion() {
		if (motion != null) {
			onComplete( motion );
		}
	}
	
	public void attack( int cell ) {
		turnTo( ch.pos, cell );
		play( attack );
	}
	
	public void attack( int cell, Callback callback ) {
		animCallback = callback;
		turnTo( ch.pos, cell );
		play( attack );
	}
	
	public void operate( int cell ) {
		turnTo( ch.pos, cell );
		play( operate );
	}
	
	public void zap( int cell ) {
		turnTo( ch.pos, cell );
		play( zap );
	}
	
	public void turnTo( int from, int to ) {
		int fx = from % Level.WIDTH;
		int tx = to % Level.WIDTH;
		if (tx > fx) {
			flipHorizontal = false;
		} else if (tx < fx) {
			flipHorizontal = true;
		}
	}
	
	public void die() {
		sleeping = false;
		play( die );
		
		if (emo != null) {
			emo.killAndErase();
		}
	}
	
	public Emitter emitter() {
		Emitter emitter = GameScene.emitter();
		emitter.pos( this );
		return emitter;
	}
	
	public Emitter centerEmitter() {
		Emitter emitter = GameScene.emitter();
		emitter.pos( center() );
		return emitter;
	}
	
	public Emitter bottomEmitter() {
		Emitter emitter = GameScene.emitter();
		emitter.pos( x, y + height, width, 0 );
		return emitter;
	}
	
	public void burst( final int color, int n ) {
		if (visible) {
			Splash.at( center(), color, n );
		}
	}
	
	public void bloodBurstA( PointF from, int damage ) {
		if (visible) {
			PointF c = center();
			int n = (int)Math.min( 9 * Math.sqrt( (double)damage / ch.HT ), 9 );
			Splash.at( c, PointF.angle( from, c ), 3.1415926f / 2, blood(), n );
		}
	}
	
	// Blood color
	public int blood() {
		return 0xFFBB0000;
	}
	
	public void flash() {
		ra = ba = ga = 1f;
		flashTime = FLASH_INTERVAL;
	}
	
	public void add( State state ) {
		switch (state) {
		case BURNING:
			burning = emitter();
			burning.pour( FlameParticle.FACTORY, 0.06f );
			if (visible) {
				Sample.INSTANCE.play( Assets.SND_BURNING );
			}
			break;
		case LEVITATING:
			levitation = emitter();
			levitation.pour( Speck.factory( Speck.JET ), 0.02f );
			break;
		case INVISIBLE:
			PotionOfInvisibility.melt( ch );
			break;
		case PARALYSED:
			paused = true;
			break;
		case FROZEN:
			iceBlock = IceBlock.freeze( this );
			paused = true;
			break;
		case ILLUMINATED:
			GameScene.effect( halo = new TorchHalo( this ) );
			break;
		}
	}
	
	public void remove( State state ) {
		switch (state) {
		case BURNING:
			if (burning != null) {
				burning.on = false;
				burning = null;
			}
			break;
		case LEVITATING:
			if (levitation != null) {
				levitation.on = false;
				levitation = null;
			}
			break;
		case INVISIBLE:
			alpha( 1f );
			break;
		case PARALYSED:
			paused = false;
			break;
		case FROZEN:
			if (iceBlock != null) {
				iceBlock.melt();
				iceBlock = null;
			}
			paused = false;
			break;
		case ILLUMINATED:
			if (halo != null) {
				halo.putOut();
			}
			break;
		}
	}
	
	@Override
	public void update() {
		
		super.update();
		
		if (paused && listener != null) {
			listener.onComplete( curAnim );
		}
		
		if (flashTime > 0 && (flashTime -= Game.elapsed) <= 0) {
			resetColor();
		}
		
		if (burning != null) {
			burning.visible = visible;
		}
		if (levitation != null) {
			levitation.visible = visible;
		}
		if (iceBlock != null) {
			iceBlock.visible = visible;
		}
		if (sleeping) {
			showSleep();
		} else {
			hideSleep();
		}
		if (emo != null) {
			emo.visible = visible;
		}
	}
	
	public void showSleep() {
		if (emo instanceof EmoIcon.Sleep) {
			
		} else {
			if (emo != null) {
				emo.killAndErase();
			}
			emo = new EmoIcon.Sleep( this );
		}
        idle();
	}
	
	public void hideSleep() {
		if (emo instanceof EmoIcon.Sleep) {
			emo.killAndErase();
			emo = null;
		}
	}
	
	public void showAlert() {
		if (emo instanceof EmoIcon.Alert) {
			
		} else {
			if (emo != null) {
				emo.killAndErase();
			}
			emo = new EmoIcon.Alert( this );
		}
	}
	
	public void hideAlert() {
		if (emo instanceof EmoIcon.Alert) {
			emo.killAndErase();
			emo = null;
		}
	}
	
	@Override
	public void kill() {
		super.kill();
		
		if (emo != null) {
			emo.killAndErase();
			emo = null;
		}
	}
	
	@Override
	public void onComplete( Tweener tweener ) {
		if (tweener == motion) {

            isMoving = false;

			motion.killAndErase();
			motion = null;
			ch.onMotionComplete();
		}
	}

	@Override
	public void onComplete( Animation anim ) {
		
		if (animCallback != null) {
			animCallback.call();
			animCallback = null;
		} else {
			
			if (anim == attack) {
				
				idle();
				ch.onAttackComplete();
				
			} else if (anim == operate) {
				
				idle();
				ch.onOperateComplete();
				
			}
			
		}
	}
} public class WndMessage extends Window {
	
	private static final int WIDTH = 120;
	private static final int MARGIN = 4;
	
	public WndMessage( String text ) {
		
		super();
		
		BitmapTextMultiline info = PixelScene.createMultiline( text, 6 );
		info.maxWidth = WIDTH - MARGIN * 2;
		info.measure();
		info.x = info.y = MARGIN;
		add( info );

		resize( 
			(int)info.width() + MARGIN * 2, 
			(int)info.height() + MARGIN * 2 );
	}
}
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.ShatteredPixelDungeon;
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.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.Light;
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.DewVial;
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.Armor;
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.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.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.ScrollOfMagicMapping;
import com.shatteredpixel.shatteredpixeldungeon.items.scrolls.ScrollOfRecharging;
import com.shatteredpixel.shatteredpixeldungeon.items.scrolls.ScrollOfUpgrade;
import com.shatteredpixel.shatteredpixeldungeon.items.wands.Wand;
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.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.HashSet;
			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 (curAction != null && 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) {
				GameScene.show( new WndMessage( Dungeon.tip() ) );
			}
			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 );