/* * Pixel Dungeon * Copyright (C) 2012-2014 Oleg Dolya * * 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.watabou.pixeldungeon.actors; import java.util.HashSet; import com.watabou.noosa.audio.Sample; import com.watabou.pixeldungeon.Assets; import com.watabou.pixeldungeon.Dungeon; import com.watabou.pixeldungeon.ResultDescriptions; import com.watabou.pixeldungeon.actors.buffs.Amok; import com.watabou.pixeldungeon.actors.buffs.Bleeding; import com.watabou.pixeldungeon.actors.buffs.Buff; import com.watabou.pixeldungeon.actors.buffs.Burning; import com.watabou.pixeldungeon.actors.buffs.Cripple; import com.watabou.pixeldungeon.actors.buffs.Frost; import com.watabou.pixeldungeon.actors.buffs.Invisibility; import com.watabou.pixeldungeon.actors.buffs.Light; import com.watabou.pixeldungeon.actors.buffs.Roots; import com.watabou.pixeldungeon.actors.buffs.Shadows; import com.watabou.pixeldungeon.actors.buffs.Sleep; import com.watabou.pixeldungeon.actors.buffs.Speed; import com.watabou.pixeldungeon.actors.buffs.Levitation; import com.watabou.pixeldungeon.actors.buffs.MindVision; import com.watabou.pixeldungeon.actors.buffs.Paralysis; import com.watabou.pixeldungeon.actors.buffs.Poison; import com.watabou.pixeldungeon.actors.buffs.Slow; import com.watabou.pixeldungeon.actors.buffs.Terror; import com.watabou.pixeldungeon.actors.hero.Hero; import com.watabou.pixeldungeon.actors.hero.HeroSubClass; import com.watabou.pixeldungeon.actors.mobs.Bestiary; import com.watabou.pixeldungeon.effects.CellEmitter; import com.watabou.pixeldungeon.effects.particles.PoisonParticle; import com.watabou.pixeldungeon.levels.Level; import com.watabou.pixeldungeon.levels.Terrain; import com.watabou.pixeldungeon.levels.features.Door; import com.watabou.pixeldungeon.sprites.CharSprite; import com.watabou.pixeldungeon.utils.GLog; import com.watabou.pixeldungeon.utils.Utils; import com.watabou.utils.Bundlable; import com.watabou.utils.Bundle; import com.watabou.utils.Random; public abstract class Char extends Actor { protected static final String TXT_HIT = "%s hit %s"; protected static final String TXT_KILL = "%s killed you..."; protected static final String TXT_DEFEAT = "%s defeated %s"; private static final String TXT_YOU_MISSED = "%s %s your attack"; private static final String TXT_SMB_MISSED = "%s %s %s's attack"; private static final String TXT_OUT_OF_PARALYSIS = "The pain snapped %s out of paralysis"; public int pos = 0; public CharSprite sprite; public String name = "mob"; public int HT; public int HP; protected float baseSpeed = 1; public boolean paralysed = false; public boolean pacified = false; public boolean rooted = false; public boolean flying = false; public int invisible = 0; public int viewDistance = 8; private HashSet buffs = new HashSet(); @Override protected boolean act() { Dungeon.level.updateFieldOfView( this ); return false; } private static final String POS = "pos"; private static final String TAG_HP = "HP"; private static final String TAG_HT = "HT"; private static final String BUFFS = "buffs"; @Override public void storeInBundle( Bundle bundle ) { super.storeInBundle( bundle ); bundle.put( POS, pos ); bundle.put( TAG_HP, HP ); bundle.put( TAG_HT, HT ); bundle.put( BUFFS, buffs ); } @Override public void restoreFromBundle( Bundle bundle ) { super.restoreFromBundle( bundle ); pos = bundle.getInt( POS ); HP = bundle.getInt( TAG_HP ); HT = bundle.getInt( TAG_HT ); for (Bundlable b : bundle.getCollection( BUFFS )) { if (b != null) { ((Buff)b).attachTo( this ); } } } public boolean attack( Char enemy ) { boolean visibleFight = Dungeon.visible[pos] || Dungeon.visible[enemy.pos]; if (hit( this, enemy, false )) { if (visibleFight) { GLog.i( TXT_HIT, name, enemy.name ); } // Refactoring needed! int dr = this instanceof Hero && ((Hero)this).usingRanged && ((Hero)this).subClass == HeroSubClass.SNIPER ? 0 : Random.IntRange( 0, enemy.dr() ); int dmg = damageRoll(); int effectiveDamage = Math.max( dmg - dr, 0 );; effectiveDamage = attackProc( enemy, effectiveDamage ); effectiveDamage = enemy.defenseProc( this, effectiveDamage ); enemy.damage( effectiveDamage, this ); if (visibleFight) { Sample.INSTANCE.play( Assets.SND_HIT, 1, 1, Random.Float( 0.8f, 1.25f ) ); } if (enemy == Dungeon.hero) { Dungeon.hero.interrupt(); } enemy.sprite.bloodBurstA( sprite.center(), effectiveDamage ); enemy.sprite.flash(); if (!enemy.isAlive() && visibleFight) { if (enemy == Dungeon.hero) { if (Dungeon.hero.killerGlyph != null) { Dungeon.fail( Utils.format( ResultDescriptions.GLYPH, Dungeon.hero.killerGlyph.name(), Dungeon.depth ) ); GLog.n( TXT_KILL, Dungeon.hero.killerGlyph.name() ); } else { if (Bestiary.isUnique( this )) { Dungeon.fail( Utils.format( ResultDescriptions.BOSS, name, Dungeon.depth ) ); } else { Dungeon.fail( Utils.format( ResultDescriptions.MOB, Utils.indefinite( name ), Dungeon.depth ) ); } GLog.n( TXT_KILL, name ); } } else { GLog.i( TXT_DEFEAT, name, enemy.name ); } } return true; } else { if (visibleFight) { String defense = enemy.defenseVerb(); enemy.sprite.showStatus( CharSprite.NEUTRAL, defense ); if (this == Dungeon.hero) { GLog.i( TXT_YOU_MISSED, enemy.name, defense ); } else { GLog.i( TXT_SMB_MISSED, enemy.name, defense, name ); } Sample.INSTANCE.play( Assets.SND_MISS ); } return false; } } public static boolean hit( Char attacker, Char defender, boolean magic ) { float acuRoll = Random.Float( attacker.attackSkill( defender ) ); float defRoll = Random.Float( defender.defenseSkill( attacker ) ); return (magic ? acuRoll * 2 : acuRoll) >= defRoll; } public int attackSkill( Char target ) { return 0; } public int defenseSkill( Char enemy ) { return 0; } public String defenseVerb() { return "dodged"; } public int dr() { return 0; } public int damageRoll() { return 1; } public int attackProc( Char enemy, int damage ) { return damage; } public int defenseProc( Char enemy, int damage ) { return damage; } public float speed() { return buff( Cripple.class ) == null ? baseSpeed : baseSpeed * 0.5f; } public void damage( int dmg, Object src ) { if (HP <= 0) { return; } Buff.detach( this, Frost.class ); Class srcClass = src.getClass(); if (immunities().contains( srcClass )) { dmg = 0; } else if (resistances().contains( srcClass )) { dmg = Random.IntRange( 0, dmg ); } if (buff( Paralysis.class ) != null) { if (Random.Int( dmg ) >= Random.Int( HP )) { Buff.detach( this, Paralysis.class ); if (Dungeon.visible[pos]) { GLog.i( TXT_OUT_OF_PARALYSIS, name ); } } } HP -= dmg; if (dmg > 0 || src instanceof Char) { sprite.showStatus( HP > HT / 2 ? CharSprite.WARNING : CharSprite.NEGATIVE, Integer.toString( dmg ) ); } if (HP <= 0) { die( src ); } } public void destroy() { HP = 0; Actor.remove( this ); Actor.freeCell( pos ); } public void die( Object src ) { destroy(); sprite.die(); } public boolean isAlive() { return HP > 0; } @Override protected void spend( float time ) { float timeScale = 1f; if (buff( Slow.class ) != null) { timeScale *= 0.5f; } if (buff( Speed.class ) != null) { timeScale *= 2.0f; } super.spend( time / timeScale ); } public HashSet buffs() { return buffs; } @SuppressWarnings("unchecked") public HashSet buffs( Class c ) { HashSet filtered = new HashSet(); for (Buff b : buffs) { if (c.isInstance( b )) { filtered.add( (T)b ); } } return filtered; } @SuppressWarnings("unchecked") public T buff( Class c ) { for (Buff b : buffs) { if (c.isInstance( b )) { return (T)b; } } return null; } public void add( Buff buff ) { buffs.add( buff ); Actor.add( buff ); if (sprite != null) { if (buff instanceof Poison) { CellEmitter.center( pos ).burst( PoisonParticle.SPLASH, 5 ); sprite.showStatus( CharSprite.NEGATIVE, "poisoned" ); } else if (buff instanceof Amok) { sprite.showStatus( CharSprite.NEGATIVE, "amok" ); } else if (buff instanceof Slow) { sprite.showStatus( CharSprite.NEGATIVE, "slowed" ); } else if (buff instanceof MindVision) { sprite.showStatus( CharSprite.POSITIVE, "mind" ); sprite.showStatus( CharSprite.POSITIVE, "vision" ); } else if (buff instanceof Paralysis) { sprite.add( CharSprite.State.PARALYSED ); sprite.showStatus( CharSprite.NEGATIVE, "paralysed" ); } else if (buff instanceof Terror) { sprite.showStatus( CharSprite.NEGATIVE, "frightened" ); } else if (buff instanceof Roots) { sprite.showStatus( CharSprite.NEGATIVE, "rooted" ); } else if (buff instanceof Cripple) { sprite.showStatus( CharSprite.NEGATIVE, "crippled" ); } else if (buff instanceof Bleeding) { sprite.showStatus( CharSprite.NEGATIVE, "bleeding" ); } else if (buff instanceof Sleep) { sprite.idle(); } else if (buff instanceof Burning) { sprite.add( CharSprite.State.BURNING ); } else if (buff instanceof Levitation) { sprite.add( CharSprite.State.LEVITATING ); } else if (buff instanceof Frost) { sprite.add( CharSprite.State.FROZEN ); } else if (buff instanceof Invisibility) { if (!(buff instanceof Shadows)) { sprite.showStatus( CharSprite.POSITIVE, "invisible" ); } sprite.add( CharSprite.State.INVISIBLE ); } } } public void remove( Buff buff ) { buffs.remove( buff ); Actor.remove( buff ); if (buff instanceof Burning) { sprite.remove( CharSprite.State.BURNING ); } else if (buff instanceof Levitation) { sprite.remove( CharSprite.State.LEVITATING ); } else if (buff instanceof Invisibility && invisible <= 0) { sprite.remove( CharSprite.State.INVISIBLE ); } else if (buff instanceof Paralysis) { sprite.remove( CharSprite.State.PARALYSED ); } else if (buff instanceof Frost) { sprite.remove( CharSprite.State.FROZEN ); } } public void remove( Class buffClass ) { for (Buff buff : buffs( buffClass )) { remove( buff ); } } @Override protected void onRemove() { for (Buff buff : buffs.toArray( new Buff[0] )) { buff.detach(); } } public void updateSpriteState() { for (Buff buff:buffs) { if (buff instanceof Burning) { sprite.add( CharSprite.State.BURNING ); } else if (buff instanceof Levitation) { sprite.add( CharSprite.State.LEVITATING ); } else if (buff instanceof Invisibility) { sprite.add( CharSprite.State.INVISIBLE ); } else if (buff instanceof Paralysis) { sprite.add( CharSprite.State.PARALYSED ); } else if (buff instanceof Frost) { sprite.add( CharSprite.State.FROZEN ); } else if (buff instanceof Light) { sprite.add( CharSprite.State.ILLUMINATED ); } } } public int stealth() { return 0; } public void move( int step ) { if (Dungeon.level.map[pos] == Terrain.OPEN_DOOR) { Door.leave( pos ); } pos = step; if (flying && Dungeon.level.map[pos] == Terrain.DOOR) { Door.enter( pos ); } if (this != Dungeon.hero) { sprite.visible = Dungeon.visible[pos]; } } public int distance( Char other ) { return Level.distance( pos, other.pos ); } public void onMotionComplete() { next(); } public void onAttackComplete() { next(); } public void onOperateComplete() { next(); } private static final HashSet> EMPTY = new HashSet>(); public HashSet> resistances() { return EMPTY; } public HashSet> immunities() { return EMPTY; } }