From 093635c454f82ca42307c5cf01a9cbed0a2ec8d6 Mon Sep 17 00:00:00 2001 From: Evan Debenham Date: Wed, 20 Jul 2016 16:29:17 -0400 Subject: [PATCH] v0.4.1: reworked evil eyes --- .../actors/mobs/Eye.java | 150 +++++++++++++----- .../effects/MagicMissile.java | 22 +++ .../messages/actors/actors.properties | 2 +- .../sprites/EyeSprite.java | 62 ++++++-- 4 files changed, 189 insertions(+), 47 deletions(-) diff --git a/src/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/Eye.java b/src/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/Eye.java index b7cab341f..ca8f99b9a 100644 --- a/src/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/Eye.java +++ b/src/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/Eye.java @@ -31,11 +31,14 @@ import com.shatteredpixel.shatteredpixeldungeon.items.Dewdrop; import com.shatteredpixel.shatteredpixeldungeon.items.wands.WandOfDisintegration; import com.shatteredpixel.shatteredpixeldungeon.items.weapon.enchantments.Grim; import com.shatteredpixel.shatteredpixeldungeon.items.weapon.enchantments.Vampiric; +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.CharSprite; import com.shatteredpixel.shatteredpixeldungeon.sprites.EyeSprite; import com.shatteredpixel.shatteredpixeldungeon.utils.GLog; +import com.watabou.utils.Bundle; import com.watabou.utils.Random; import java.util.HashSet; @@ -53,12 +56,24 @@ public class Eye extends Mob { maxLvl = 25; flying = true; + + HUNTING = new Hunting(); loot = new Dewdrop(); lootChance = 0.5f; properties.add(Property.DEMONIC); } + + @Override + public int damageRoll() { + return Random.NormalIntRange(20, 30); + } + + @Override + public int attackSkill( Char target ) { + return 30; + } @Override public int drRoll() { @@ -66,65 +81,89 @@ public class Eye extends Mob { } private Ballistica beam; + private int beamCooldown; + public boolean beamCharged; @Override protected boolean canAttack( Char enemy ) { - - beam = new Ballistica( pos, enemy.pos, Ballistica.STOP_TERRAIN); - return beam.subPath(1, beam.dist).contains(enemy.pos); + if (beamCooldown == 0) { + Ballistica aim = new Ballistica(pos, enemy.pos, Ballistica.STOP_TERRAIN); + + if (enemy.invisible == 0 && aim.subPath(1, aim.dist).contains(enemy.pos)){ + beam = aim; + return true; + } else + //if the beam is charged, it has to attack, will aim at previous location of hero. + return beamCharged; + } else + return super.canAttack(enemy); } - + @Override - public int attackSkill( Char target ) { - return 30; - } - - @Override - protected float attackDelay() { - return 1.6f; + protected boolean act() { + if (beamCooldown > 0) + beamCooldown--; + return super.act(); } @Override protected boolean doAttack( Char enemy ) { - spend( attackDelay() ); - - boolean rayVisible = false; - - for (int i : beam.subPath(0, beam.dist)) { - if (Dungeon.visible[i]) { - rayVisible = true; + if (beamCooldown > 0) { + return super.doAttack(enemy); + } else if (!beamCharged){ + ((EyeSprite)sprite).charge( enemy.pos ); + spend( attackDelay()*2f ); + beamCharged = true; + return true; + } else { + + spend( attackDelay() ); + + if (Dungeon.visible[pos]) { + sprite.zap( beam.collisionPos ); + return false; + } else { + deathGaze(); + return true; } } - - if (rayVisible) { - sprite.attack( beam.collisionPos ); - return false; - } else { - attack( enemy ); - return true; - } + } - - @Override - public boolean attack( Char enemy ) { - + + public void deathGaze(){ + if (!beamCharged || beamCooldown > 0 || beam == null) + return; + + beamCharged = false; + beamCooldown = Random.IntRange(3, 6); + + boolean terrainAffected = false; + for (int pos : beam.subPath(1, beam.dist)) { + if (Level.flamable[pos]) { + + Dungeon.level.destroy( pos ); + GameScene.updateMap( pos ); + terrainAffected = true; + + } + Char ch = Actor.findChar( pos ); if (ch == null) { continue; } - + if (hit( this, ch, true )) { - ch.damage( Random.NormalIntRange( 14, 20 ), this ); - + ch.damage( Random.NormalIntRange( 30, 40 ), this ); + if (Dungeon.visible[pos]) { ch.sprite.flash(); CellEmitter.center( pos ).burst( PurpleParticle.BURST, Random.IntRange( 1, 2 ) ); } - + if (!ch.isAlive() && ch == Dungeon.hero) { Dungeon.fail( getClass() ); GLog.n( Messages.get(this, "deathgaze_kill") ); @@ -133,10 +172,37 @@ public class Eye extends Mob { ch.sprite.showStatus( CharSprite.NEUTRAL, ch.defenseVerb() ); } } - - return true; + + if (terrainAffected) { + Dungeon.observe(); + } + + beam = null; + sprite.idle(); } - + + private static final String BEAM_TARGET = "beamTarget"; + private static final String BEAM_COOLDOWN = "beamCooldown"; + private static final String BEAM_CHARGED = "beamCharged"; + + @Override + public void storeInBundle(Bundle bundle) { + super.storeInBundle(bundle); + if (beam != null) + bundle.put( BEAM_TARGET, beam.collisionPos); + bundle.put( BEAM_COOLDOWN, beamCooldown ); + bundle.put( BEAM_CHARGED, beamCharged ); + } + + @Override + public void restoreFromBundle(Bundle bundle) { + super.restoreFromBundle(bundle); + if (bundle.contains(BEAM_TARGET)) + beam = new Ballistica(pos, bundle.getInt(BEAM_TARGET), Ballistica.STOP_TERRAIN); + beamCooldown = bundle.getInt(BEAM_COOLDOWN); + beamCharged = bundle.getBoolean(BEAM_CHARGED); + } + private static final HashSet> RESISTANCES = new HashSet<>(); static { RESISTANCES.add( WandOfDisintegration.class ); @@ -158,4 +224,14 @@ public class Eye extends Mob { public HashSet> immunities() { return IMMUNITIES; } + + private class Hunting extends Mob.Hunting{ + @Override + public boolean act(boolean enemyInFOV, boolean justAlerted) { + //always attack if the beam is charged, no exceptions + if (beamCharged) + enemyInFOV = true; + return super.act(enemyInFOV, justAlerted); + } + } } diff --git a/src/com/shatteredpixel/shatteredpixeldungeon/effects/MagicMissile.java b/src/com/shatteredpixel/shatteredpixeldungeon/effects/MagicMissile.java index 824d20e3e..6c4d90a63 100644 --- a/src/com/shatteredpixel/shatteredpixeldungeon/effects/MagicMissile.java +++ b/src/com/shatteredpixel/shatteredpixeldungeon/effects/MagicMissile.java @@ -192,6 +192,17 @@ public class MagicMissile extends Emitter { return true; }; }; + + public static final Emitter.Factory ATTRACTING = new Factory() { + @Override + public void emit( Emitter emitter, int index, float x, float y ) { + ((MagicParticle)emitter.recycle( MagicParticle.class )).resetAttract( x, y ); + } + @Override + public boolean lightMode() { + return true; + }; + }; public MagicParticle() { super(); @@ -210,6 +221,17 @@ public class MagicMissile extends Emitter { left = lifespan; } + + public void resetAttract( float x, float y) { + revive(); + + //size = 8; + left = lifespan; + + speed.polar( Random.Float( PointF.PI2 ), Random.Float( 16, 32 ) ); + this.x = x - speed.x * lifespan; + this.y = y - speed.y * lifespan; + } @Override public void update() { diff --git a/src/com/shatteredpixel/shatteredpixeldungeon/messages/actors/actors.properties b/src/com/shatteredpixel/shatteredpixeldungeon/messages/actors/actors.properties index c8444d578..0b980b471 100644 --- a/src/com/shatteredpixel/shatteredpixeldungeon/messages/actors/actors.properties +++ b/src/com/shatteredpixel/shatteredpixeldungeon/messages/actors/actors.properties @@ -384,7 +384,7 @@ actors.mobs.elemental.desc=Wandering fire elementals are a byproduct of summonin actors.mobs.eye.name=evil eye actors.mobs.eye.deathgaze_kill=The deathgaze killed you... -actors.mobs.eye.desc=One of this demon's other names is "orb of hatred", because when it sees an enemy, it uses its deathgaze recklessly, often ignoring its allies and wounding them. +actors.mobs.eye.desc=Evil Eyes are floating balls of pent up demonic energy. While they are capable of melee combat, their true strength comes from their magic.\n\nAfter building energy for a short time an Evil Eye will unleash a devastating beam of energy called the _deathgaze._ Anything within the Evil Eye's sights will take tremendous damage, wise adventurers will run for cover. actors.mobs.fetidrat.name=fetid rat actors.mobs.fetidrat.desc=Something is clearly wrong with this rat. Its greasy black fur and rotting skin are very different from the healthy rats you've seen previously. It's pale green eyes make it seem especially menacing.\n\nThe rat carries a cloud of horrible stench with it, it's overpoweringly strong up close.\n\nDark ooze dribbles from the rat's mouth, it eats through the floor but seems to dissolve in water. diff --git a/src/com/shatteredpixel/shatteredpixeldungeon/sprites/EyeSprite.java b/src/com/shatteredpixel/shatteredpixeldungeon/sprites/EyeSprite.java index 548f2a83d..30eafc187 100644 --- a/src/com/shatteredpixel/shatteredpixeldungeon/sprites/EyeSprite.java +++ b/src/com/shatteredpixel/shatteredpixeldungeon/sprites/EyeSprite.java @@ -23,12 +23,19 @@ package com.shatteredpixel.shatteredpixeldungeon.sprites; import com.shatteredpixel.shatteredpixeldungeon.Assets; import com.shatteredpixel.shatteredpixeldungeon.Dungeon; import com.shatteredpixel.shatteredpixeldungeon.DungeonTilemap; +import com.shatteredpixel.shatteredpixeldungeon.actors.Char; +import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Eye; import com.shatteredpixel.shatteredpixeldungeon.effects.Beam; +import com.shatteredpixel.shatteredpixeldungeon.effects.MagicMissile; import com.watabou.noosa.TextureFilm; +import com.watabou.noosa.particles.Emitter; public class EyeSprite extends MobSprite { - - private int attackPos; + + private int zapPos; + + private Animation charging; + private Emitter chargeParticles; public EyeSprite() { super(); @@ -39,33 +46,70 @@ public class EyeSprite extends MobSprite { idle = new Animation( 8, true ); idle.frames( frames, 0, 1, 2 ); + + charging = new Animation( 12, true); + charging.frames( frames, 3, 4 ); + + chargeParticles = centerEmitter(); + chargeParticles.autoKill = false; + chargeParticles.pour(MagicMissile.MagicParticle.ATTRACTING, 0.05f); + chargeParticles.on = false; run = new Animation( 12, true ); run.frames( frames, 5, 6 ); attack = new Animation( 8, false ); attack.frames( frames, 4, 3 ); + zap = attack.clone(); die = new Animation( 8, false ); die.frames( frames, 7, 8, 9 ); play( idle ); } - + @Override - public void attack( int pos ) { - attackPos = pos; - super.attack( pos ); + public void link(Char ch) { + super.link(ch); + if (((Eye)ch).beamCharged) play(charging); + } + + @Override + public void update() { + super.update(); + chargeParticles.pos(center()); + chargeParticles.visible = visible; + } + + public void charge( int pos ){ + turnTo(ch.pos, pos); + play(charging); + } + + @Override + public void play(Animation anim) { + chargeParticles.on = anim == charging; + super.play(anim); + } + + @Override + public void zap( int pos ) { + zapPos = pos; + super.zap( pos ); } @Override public void onComplete( Animation anim ) { super.onComplete( anim ); - if (anim == attack) { - if (Dungeon.visible[ch.pos] || Dungeon.visible[attackPos]) { - parent.add( new Beam.DeathRay( center(), DungeonTilemap.tileCenterToWorld( attackPos ) ) ); + if (anim == zap) { + if (Dungeon.visible[ch.pos] || Dungeon.visible[zapPos]) { + parent.add( new Beam.DeathRay( center(), DungeonTilemap.tileCenterToWorld( zapPos ) ) ); } + ((Eye)ch).deathGaze(); + ch.next(); + } else if (anim == die){ + chargeParticles.killAndErase(); } } }