From a4db73be1aea88ce4219f1aa310c150b9f65afb1 Mon Sep 17 00:00:00 2001 From: Evan Debenham Date: Wed, 20 Sep 2023 16:59:26 -0400 Subject: [PATCH] v2.2.0: more crystal enemy impl --- .../actors/mobs/CrystalGuardian.java | 76 +++++++++++++--- .../actors/mobs/CrystalWisp.java | 88 ++++++++++++++++++- .../sprites/CrystalGuardianSprite.java | 18 ++++ .../sprites/CrystalWispSprite.java | 20 +++++ 4 files changed, 187 insertions(+), 15 deletions(-) diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/CrystalGuardian.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/CrystalGuardian.java index 1b78362a9..7c931931a 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/CrystalGuardian.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/CrystalGuardian.java @@ -21,11 +21,17 @@ package com.shatteredpixel.shatteredpixeldungeon.actors.mobs; +import com.shatteredpixel.shatteredpixeldungeon.Assets; import com.shatteredpixel.shatteredpixeldungeon.Dungeon; +import com.shatteredpixel.shatteredpixeldungeon.actors.Char; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Healing; +import com.shatteredpixel.shatteredpixeldungeon.effects.Splash; import com.shatteredpixel.shatteredpixeldungeon.levels.Level; import com.shatteredpixel.shatteredpixeldungeon.levels.Terrain; import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene; import com.shatteredpixel.shatteredpixeldungeon.sprites.CrystalGuardianSprite; +import com.watabou.noosa.audio.Sample; import com.watabou.utils.Bundle; import com.watabou.utils.PathFinder; import com.watabou.utils.Random; @@ -33,11 +39,34 @@ import com.watabou.utils.Random; public class CrystalGuardian extends Mob{ { - HP = HT = 1; spriteClass = CrystalGuardianSprite.class; + HP = HT = 100; + defenseSkill = 14; + + EXP = 10; + maxLvl = -2; + SLEEPING = new Sleeping(); state = SLEEPING; + + properties.add(Property.INORGANIC); + properties.add(Property.MINIBOSS); + } + + @Override + public int damageRoll() { + return Random.NormalIntRange( 10, 20 ); + } + + @Override + public int attackSkill( Char target ) { + return 20; + } + + @Override + public int drRoll() { + return super.drRoll() + Random.NormalIntRange(0, 10); } @Override @@ -45,6 +74,17 @@ public class CrystalGuardian extends Mob{ return true; } + @Override + public boolean isAlive() { + if (HP <= 0){ + HP = 1; + if (sprite != null) ((CrystalGuardianSprite)sprite).crumple(); + spend(10f-cooldown()); + Buff.affect(this, Healing.class).setHeal(100, 0, 10); + } + return super.isAlive(); + } + public CrystalGuardian(){ super(); switch (Random.Int(3)){ @@ -77,24 +117,32 @@ public class CrystalGuardian extends Mob{ @Override public void beckon(int cell) { super.beckon(cell); + state = HUNTING; + } - //If we are still penned into our starting area, break out of it - PathFinder.buildDistanceMap(cell, Dungeon.level.passable); - if (PathFinder.distance[pos] == Integer.MAX_VALUE){ - boolean[] passable = Dungeon.level.passable.clone(); + @Override + public void move(int step, boolean travelling) { + super.move(step, travelling); + if (Dungeon.level.map[pos] == Terrain.MINE_CRYSTAL){ + Level.set(pos, Terrain.EMPTY); + GameScene.updateMap(pos); + if (Dungeon.level.heroFOV[pos]){ + Splash.at(pos, 0xFFFFFF, 5); + Sample.INSTANCE.play( Assets.Sounds.SHATTER ); + } + } + } + + @Override + public boolean[] modifyPassable(boolean[] passable) { + //TODO maybe base this on passable instead of hunting? + // want to prevent one guardian randomly waking another though + if (state == HUNTING){ for (int i = 0; i < Dungeon.level.length(); i++){ passable[i] = passable[i] || Dungeon.level.map[i] == Terrain.MINE_CRYSTAL; } - PathFinder.Path p = PathFinder.find(pos, cell, passable); - if (p != null) { - for (int i : p) { - if (Dungeon.level.map[i] == Terrain.MINE_CRYSTAL) { - Level.set(i, Terrain.EMPTY); - GameScene.updateMap(i); - } - } - } } + return passable; } protected class Sleeping extends Mob.Sleeping{ diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/CrystalWisp.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/CrystalWisp.java index 99ef1f84d..13de3753d 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/CrystalWisp.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/CrystalWisp.java @@ -21,17 +21,34 @@ package com.shatteredpixel.shatteredpixeldungeon.actors.mobs; +import com.shatteredpixel.shatteredpixeldungeon.Badges; import com.shatteredpixel.shatteredpixeldungeon.Dungeon; +import com.shatteredpixel.shatteredpixeldungeon.actors.Char; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.AscensionChallenge; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Invisibility; import com.shatteredpixel.shatteredpixeldungeon.levels.Terrain; +import com.shatteredpixel.shatteredpixeldungeon.mechanics.Ballistica; +import com.shatteredpixel.shatteredpixeldungeon.messages.Messages; +import com.shatteredpixel.shatteredpixeldungeon.sprites.CharSprite; import com.shatteredpixel.shatteredpixeldungeon.sprites.CrystalWispSprite; +import com.shatteredpixel.shatteredpixeldungeon.utils.GLog; import com.watabou.utils.Bundle; import com.watabou.utils.Random; public class CrystalWisp extends Mob{ { - HP = HT = 1; spriteClass = CrystalWispSprite.class; + + HP = HT = 35; + defenseSkill = 16; + + EXP = 7; + maxLvl = -2; + + flying = true; + + properties.add(Property.INORGANIC); } public CrystalWisp(){ @@ -57,6 +74,75 @@ public class CrystalWisp extends Mob{ return passable; } + @Override + public int damageRoll() { + return Random.NormalIntRange( 3, 10 ); + } + + @Override + public int attackSkill( Char target ) { + return 16; + } + + @Override + public int drRoll() { + return super.drRoll() + Random.NormalIntRange(0, 5); + } + + @Override + protected boolean canAttack( Char enemy ) { + return super.canAttack(enemy) + || new Ballistica( pos, enemy.pos, Ballistica.MAGIC_BOLT).collisionPos == enemy.pos; + } + + protected boolean doAttack(Char enemy ) { + + if (Dungeon.level.adjacent( pos, enemy.pos ) + || new Ballistica( pos, enemy.pos, Ballistica.MAGIC_BOLT).collisionPos != enemy.pos) { + + return super.doAttack( enemy ); + + } else { + + if (sprite != null && (sprite.visible || enemy.sprite.visible)) { + sprite.zap( enemy.pos ); + return false; + } else { + zap(); + return true; + } + } + } + + //used so resistances can differentiate between melee and magical attacks + public static class CrystalBolt{} + + private void zap() { + spend( 1f ); + + Invisibility.dispel(this); + Char enemy = this.enemy; + if (hit( this, enemy, true )) { + + int dmg = Random.NormalIntRange( 2, 8 ); + dmg = Math.round(dmg * AscensionChallenge.statModifier(this)); + enemy.damage( dmg, new CrystalBolt() ); + + if (!enemy.isAlive() && enemy == Dungeon.hero) { + Badges.validateDeathFromEnemyMagic(); + Dungeon.fail( this ); + GLog.n( Messages.get(this, "bolt_kill") ); + } + } else { + enemy.sprite.showStatus( CharSprite.NEUTRAL, enemy.defenseVerb() ); + } + } + + public void onZapComplete() { + zap(); + next(); + } + public static final String SPRITE = "sprite"; @Override diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/CrystalGuardianSprite.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/CrystalGuardianSprite.java index 889e16c6e..83ebeade8 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/CrystalGuardianSprite.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/CrystalGuardianSprite.java @@ -22,11 +22,15 @@ package com.shatteredpixel.shatteredpixeldungeon.sprites; import com.shatteredpixel.shatteredpixeldungeon.Assets; +import com.shatteredpixel.shatteredpixeldungeon.actors.Char; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Healing; import com.watabou.noosa.MovieClip; import com.watabou.noosa.TextureFilm; public abstract class CrystalGuardianSprite extends MobSprite { + private Animation crumple; + public CrystalGuardianSprite() { super(); @@ -48,9 +52,23 @@ public abstract class CrystalGuardianSprite extends MobSprite { die = new MovieClip.Animation( 5, false ); die.frames( frames, 11+c, 12+c, 13+c, 14+c, 15+c, 15+c ); + crumple = die.clone(); + play( idle ); } + public void crumple(){ + play(crumple); + } + + @Override + public void link(Char ch) { + super.link(ch); + if (ch.buff(Healing.class) != null && ch.cooldown() > 0){ + crumple(); + } + } + protected abstract int texOffset(); public static class Blue extends CrystalGuardianSprite { diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/CrystalWispSprite.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/CrystalWispSprite.java index 829a89c3a..a5ea96294 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/CrystalWispSprite.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/CrystalWispSprite.java @@ -22,6 +22,9 @@ package com.shatteredpixel.shatteredpixeldungeon.sprites; import com.shatteredpixel.shatteredpixeldungeon.Assets; +import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.CrystalWisp; +import com.shatteredpixel.shatteredpixeldungeon.effects.Beam; +import com.shatteredpixel.shatteredpixeldungeon.tiles.DungeonTilemap; import com.watabou.noosa.TextureFilm; public abstract class CrystalWispSprite extends MobSprite { @@ -52,6 +55,23 @@ public abstract class CrystalWispSprite extends MobSprite { play( idle ); } + public void zap( int cell ) { + + super.zap( cell ); + + ((CrystalWisp)ch).onZapComplete(); + parent.add( new Beam.LightRay(center(), DungeonTilemap.raisedTileCenterToWorld(cell))); + + } + + @Override + public void onComplete( Animation anim ) { + if (anim == zap) { + idle(); + } + super.onComplete( anim ); + } + protected abstract int texOffset(); public static class Blue extends CrystalWispSprite {