From c68f27c7745cd2390c25e8f53f6035126cf57482 Mon Sep 17 00:00:00 2001 From: Evan Debenham Date: Thu, 14 Nov 2019 12:10:25 -0500 Subject: [PATCH] v0.8.0: redesigned cave spinners --- .../actors/blobs/Web.java | 38 +++++-- .../actors/mobs/Spinner.java | 101 ++++++++++++++++-- .../effects/particles/WebParticle.java | 2 +- .../shatteredpixeldungeon/levels/Level.java | 20 +++- .../sprites/SpinnerSprite.java | 33 ++++++ .../messages/actors/actors.properties | 4 +- 6 files changed, 177 insertions(+), 21 deletions(-) diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/blobs/Web.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/blobs/Web.java index 1a7504962..fd8ee8fe5 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/blobs/Web.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/blobs/Web.java @@ -28,39 +28,59 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Roots; import com.shatteredpixel.shatteredpixeldungeon.effects.BlobEmitter; import com.shatteredpixel.shatteredpixeldungeon.effects.particles.WebParticle; +import com.shatteredpixel.shatteredpixeldungeon.levels.Level; +import com.shatteredpixel.shatteredpixeldungeon.levels.Terrain; import com.shatteredpixel.shatteredpixeldungeon.messages.Messages; public class Web extends Blob { + + { + //acts before the hero, to ensure terrain is adjusted correctly + actPriority = HERO_PRIO+1; + } @Override protected void evolve() { int cell; + Level l = Dungeon.level; for (int i = area.left; i < area.right; i++){ for (int j = area.top; j < area.bottom; j++){ - cell = i + j*Dungeon.level.width(); + cell = i + j*l.width(); off[cell] = cur[cell] > 0 ? cur[cell] - 1 : 0; - if (off[cell] > 0) { + volume += off[cell]; - volume += off[cell]; - - Char ch = Actor.findChar( cell ); - if (ch != null && !ch.isImmune(this.getClass())) { - Buff.prolong( ch, Roots.class, TICK ); - } - } + l.solid[cell] = off[cell] > 0 || (Terrain.flags[l.map[cell]] & Terrain.SOLID) != 0; } } } + //affects characters as they step on it. See Level.OccupyCell and Level.PressCell + public static void affectChar( Char ch ){ + Buff.prolong( ch, Roots.class, 5f ); + } + @Override public void use( BlobEmitter emitter ) { super.use( emitter ); emitter.pour( WebParticle.FACTORY, 0.4f ); } + + @Override + public void clear(int cell) { + super.clear(cell); + Level l = Dungeon.level; + l.solid[cell] = cur[cell] > 0 || (Terrain.flags[l.map[cell]] & Terrain.SOLID) != 0; + } + + @Override + public void fullyClear() { + super.fullyClear(); + Dungeon.level.buildFlagMaps(); + } @Override public String tileDesc() { diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/Spinner.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/Spinner.java index 92b19ecfd..730fc6a03 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/Spinner.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/Spinner.java @@ -21,6 +21,7 @@ package com.shatteredpixel.shatteredpixeldungeon.actors.mobs; +import com.shatteredpixel.shatteredpixeldungeon.Dungeon; import com.shatteredpixel.shatteredpixeldungeon.actors.Char; import com.shatteredpixel.shatteredpixeldungeon.actors.blobs.Blob; import com.shatteredpixel.shatteredpixeldungeon.actors.blobs.Web; @@ -28,8 +29,10 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Poison; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Terror; import com.shatteredpixel.shatteredpixeldungeon.items.food.MysteryMeat; +import com.shatteredpixel.shatteredpixeldungeon.mechanics.Ballistica; import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene; import com.shatteredpixel.shatteredpixeldungeon.sprites.SpinnerSprite; +import com.watabou.utils.PathFinder; import com.watabou.utils.Random; public class Spinner extends Mob { @@ -38,7 +41,7 @@ public class Spinner extends Mob { spriteClass = SpinnerSprite.class; HP = HT = 50; - defenseSkill = 14; + defenseSkill = 17; EXP = 9; maxLvl = 17; @@ -56,21 +59,35 @@ public class Spinner extends Mob { @Override public int attackSkill(Char target) { - return 20; + return 22; } @Override public int drRoll() { return Random.NormalIntRange(0, 6); } - + + private int webCoolDown = 0; + private int lastEnemyPos = -1; + @Override protected boolean act() { boolean result = super.act(); - + + webCoolDown--; + if (shotWebVisually){ + result = shotWebVisually = false; + } else { + if (enemy != null && enemySeen) { + lastEnemyPos = enemy.pos; + } else { + lastEnemyPos = -1; + } + } + if (state == FLEEING && buff( Terror.class ) == null && enemy != null && enemySeen && enemy.buff( Poison.class ) == null) { - state = HUNTING; + state = HUNTING; } return result; } @@ -80,20 +97,88 @@ public class Spinner extends Mob { damage = super.attackProc( enemy, damage ); if (Random.Int(2) == 0) { Buff.affect(enemy, Poison.class).set(Random.Int(7, 9) ); + webCoolDown = 0; state = FLEEING; } return damage; } + + private boolean shotWebVisually = false; @Override public void move(int step) { - int curWeb = Blob.volumeAt(pos, Web.class); - if (state == FLEEING && curWeb < 5) { - GameScene.add(Blob.seed(pos, Random.Int(5, 7) - curWeb, Web.class)); + if (enemySeen && webCoolDown <= 0 && lastEnemyPos != -1){ + if (webPos() != -1){ + if (sprite != null && (sprite.visible || enemy.sprite.visible)) { + sprite.zap( webPos() ); + shotWebVisually = true; + } else { + shootWeb(); + } + } } super.move(step); } + + public int webPos(){ + + Ballistica b; + //aims web in direction enemy is moving, or between self and enemy if they aren't moving + if (lastEnemyPos == enemy.pos){ + b = new Ballistica( enemy.pos, pos, Ballistica.WONT_STOP ); + } else { + b = new Ballistica( lastEnemyPos, enemy.pos, Ballistica.WONT_STOP ); + } + + int collisionIndex = 0; + for (int i = 0; i < b.path.size(); i++){ + if (b.path.get(i) == enemy.pos){ + collisionIndex = i; + break; + } + } + + int webPos = b.path.get( collisionIndex+1 ); + + if (Dungeon.level.passable[webPos]){ + return webPos; + } else { + return -1; + } + + } + + public void shootWeb(){ + int webPos = webPos(); + if (webPos != enemy.pos && webPos != -1){ + int i; + for ( i = 0; i < PathFinder.CIRCLE8.length; i++){ + if ((enemy.pos + PathFinder.CIRCLE8[i]) == webPos){ + break; + } + } + + //spread to the tile hero was moving towards and the two adjacent ones + int leftPos = enemy.pos + PathFinder.CIRCLE8[left(i)]; + int rightPos = enemy.pos + PathFinder.CIRCLE8[right(i)]; + + if (Dungeon.level.passable[leftPos]) GameScene.add(Blob.seed(leftPos, 20, Web.class)); + if (Dungeon.level.passable[webPos]) GameScene.add(Blob.seed(webPos, 20, Web.class)); + if (Dungeon.level.passable[rightPos])GameScene.add(Blob.seed(rightPos, 20, Web.class)); + + webCoolDown = 10; + } + next(); + } + + private int left(int direction){ + return direction == 0 ? 7 : direction-1; + } + + private int right(int direction){ + return direction == 7 ? 0 : direction+1; + } { resistances.add(Poison.class); diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/effects/particles/WebParticle.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/effects/particles/WebParticle.java index 7bd6de541..982a5bc22 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/effects/particles/WebParticle.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/effects/particles/WebParticle.java @@ -60,6 +60,6 @@ public class WebParticle extends PixelParticle { float p = left / lifespan; am = p < 0.5f ? p : 1 - p; - scale.y = 16 + p * 8; + scale.y = 12 + p * 6; } } \ No newline at end of file diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/Level.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/Level.java index 8742e449b..75288e7b5 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/Level.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/Level.java @@ -30,6 +30,7 @@ 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.SmokeScreen; +import com.shatteredpixel.shatteredpixeldungeon.actors.blobs.Web; import com.shatteredpixel.shatteredpixeldungeon.actors.blobs.WellWater; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Awareness; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Blindness; @@ -37,6 +38,7 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.LockedFloor; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.MagicalSight; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.MindVision; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Roots; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Shadows; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.HeroClass; @@ -586,7 +588,14 @@ public abstract class Level implements Bundlable { SmokeScreen s = (SmokeScreen)blobs.get(SmokeScreen.class); if (s != null && s.volume > 0){ for (int i=0; i < length(); i++) { - losBlocking[i] = losBlocking[i] || s.cur[i] > 0; + losBlocking[i] = losBlocking[i] || s.cur[i] > 0; + } + } + + Web w = (Web) blobs.get(Web.class); + if (w != null && w.volume > 0){ + for (int i=0; i < length(); i++) { + solid[i] = solid[i] || w.cur[i] > 0; } } @@ -770,6 +779,11 @@ public abstract class Level implements Bundlable { } public void occupyCell( Char ch ){ + if (!ch.isImmune(Web.class) && Blob.volumeAt(ch.pos, Web.class) > 0){ + blobs.get(Web.class).clear(ch.pos); + Web.affectChar( ch ); + } + if (!ch.flying){ if (pit[ch.pos]){ @@ -867,6 +881,10 @@ public abstract class Level implements Bundlable { if (plant != null) { plant.trigger(); } + + if (hard && Blob.volumeAt(cell, Web.class) > 0){ + blobs.get(Web.class).clear(cell); + } } public void updateFieldOfView( Char c, boolean[] fieldOfView ) { diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/SpinnerSprite.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/SpinnerSprite.java index 5d075928f..8ccdc9243 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/SpinnerSprite.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/SpinnerSprite.java @@ -23,8 +23,13 @@ package com.shatteredpixel.shatteredpixeldungeon.sprites; import com.shatteredpixel.shatteredpixeldungeon.Assets; import com.shatteredpixel.shatteredpixeldungeon.actors.Char; +import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Spinner; +import com.shatteredpixel.shatteredpixeldungeon.effects.MagicMissile; import com.watabou.noosa.TextureFilm; +import com.watabou.noosa.audio.Sample; +import com.watabou.utils.Callback; +//TODO improvements here public class SpinnerSprite extends MobSprite { public SpinnerSprite() { @@ -45,6 +50,8 @@ public class SpinnerSprite extends MobSprite { attack = new Animation( 12, false ); attack.frames( frames, 0, 4, 5, 0 ); + zap = attack.clone(); + die = new Animation( 12, false ); die.frames( frames, 6, 7, 8, 9 ); @@ -57,6 +64,32 @@ public class SpinnerSprite extends MobSprite { if (parent != null) parent.sendToBack(this); renderShadow = false; } + + public void zap( int cell ) { + + turnTo( ch.pos , cell ); + play( zap ); + + MagicMissile.boltFromChar( parent, + MagicMissile.MAGIC_MISSILE, + this, + cell, + new Callback() { + @Override + public void call() { + ((Spinner)ch).shootWeb(); + } + } ); + Sample.INSTANCE.play( Assets.SND_MISS ); + } + + @Override + public void onComplete( Animation anim ) { + if (anim == zap) { + play( run ); + } + super.onComplete( anim ); + } @Override public int blood() { diff --git a/core/src/main/resources/com/shatteredpixel/shatteredpixeldungeon/messages/actors/actors.properties b/core/src/main/resources/com/shatteredpixel/shatteredpixeldungeon/messages/actors/actors.properties index 49430f82f..c814a1980 100644 --- a/core/src/main/resources/com/shatteredpixel/shatteredpixeldungeon/messages/actors/actors.properties +++ b/core/src/main/resources/com/shatteredpixel/shatteredpixeldungeon/messages/actors/actors.properties @@ -39,7 +39,7 @@ actors.blobs.waterofhealth.desc=Power of health radiates from the water of this actors.blobs.wateroftransmutation.desc=Power of change radiates from the water of this well. Throw an item into the well to turn it into something else. -actors.blobs.web.desc=Everything is covered with a thick web here. +actors.blobs.web.desc=A thick web is covering everything here. Anything that touches or is thrown though the web will break it, but will also be stuck in place. @@ -596,7 +596,7 @@ actors.mobs.snake.name=sewer snake actors.mobs.snake.desc=These oversized serpents are capable of quickly slithering around blows, making them quite hard to hit. Magical attacks or surprise attacks are capable of catching them off-guard however.\n\nYou can perform a surprise attack by attacking while out of the snake's vision. One way is to let a snake chase you through a doorway and then _strike just as it moves into the door._ actors.mobs.spinner.name=cave spinner -actors.mobs.spinner.desc=These greenish furry cave spiders try to avoid direct combat, preferring to wait in the distance while their victim, entangled in the spinner's excreted cobweb, slowly dies from their poisonous bite. +actors.mobs.spinner.desc=These greenish furry cave spiders try to avoid direct combat. Instead they prefer to wait in the distance while their victim struggles in their excreted cobweb, slowly dieing from their poisonous bite. They are capable of shooting their webs great distances, and will try to block whatever path their prey is taking. actors.mobs.statue.name=animated statue actors.mobs.statue.def_verb=blocked