From c3c4cc9f543c5d82ab6f3f07e18ca0896b756069 Mon Sep 17 00:00:00 2001 From: Evan Debenham Date: Wed, 29 Nov 2023 16:31:16 -0500 Subject: [PATCH] v2.3.0: implemented gnoll geomancer's interaction with living sappers --- .../actors/mobs/GnollGeomancer.java | 83 +++++++++++++++---- .../actors/mobs/GnollSapper.java | 40 ++++++--- .../effects/particles/EarthParticle.java | 4 + .../levels/rooms/quest/MineLargeRoom.java | 2 +- .../levels/traps/GnollRockfallTrap.java | 6 +- .../sprites/GnollGeomancerSprite.java | 61 ++++++++++++++ .../sprites/GnollGuardSprite.java | 6 +- 7 files changed, 165 insertions(+), 37 deletions(-) diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/GnollGeomancer.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/GnollGeomancer.java index c989d2fce..af3c323af 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/GnollGeomancer.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/GnollGeomancer.java @@ -39,6 +39,7 @@ import com.shatteredpixel.shatteredpixeldungeon.effects.TargetedCell; import com.shatteredpixel.shatteredpixeldungeon.items.Item; import com.shatteredpixel.shatteredpixeldungeon.items.quest.DarkGold; import com.shatteredpixel.shatteredpixeldungeon.items.quest.Pickaxe; +import com.shatteredpixel.shatteredpixeldungeon.items.scrolls.ScrollOfTeleportation; import com.shatteredpixel.shatteredpixeldungeon.items.wands.WandOfBlastWave; import com.shatteredpixel.shatteredpixeldungeon.levels.Level; import com.shatteredpixel.shatteredpixeldungeon.levels.Terrain; @@ -89,7 +90,8 @@ public class GnollGeomancer extends Mob { private int throwingRockFromPos = -1; private int throwingRockToPos = -1; - int[] sapperSpawns = null; + private int sapperID = -1; + private int[] sapperSpawns = null; @Override protected boolean act() { @@ -121,7 +123,9 @@ public class GnollGeomancer extends Mob { @Override public boolean isInvulnerable(Class effect) { - return super.isInvulnerable(effect) || (buff(RockArmor.class) != null && effect != Pickaxe.class); + return super.isInvulnerable(effect) + || (buff(RockArmor.class) != null && effect != Pickaxe.class) + || hasSapper(); } @Override @@ -251,11 +255,7 @@ public class GnollGeomancer extends Mob { return super.isAlive() || !inFinalBracket; } - //we pick location to jump to based on sapper positions: - //first aim for closest living sapper, moving 8-12 tiles, preferring to go 10 - //otherwise aim to closest dead sapper pos - //if we arrive at (or are near enough to) a sapper pos, we claim that sapper - // + //aim for closest sapper, preferring living ones within 16 tiles private int getDashPos(){ int closestSapperPos = -1; @@ -284,16 +284,13 @@ public class GnollGeomancer extends Mob { } } - if (Dungeon.level.distance(pos, sapperSpawns[i]) <= 16) { - if (sapperAlive && !closestisAlive - || Dungeon.level.distance(pos, sapperSpawns[i]) < Dungeon.level.distance(pos, closestSapperPos)) { - closestSapperPos = sapperSpawns[i]; - closestisAlive = sapperAlive; - } + if ((sapperAlive && !closestisAlive && Dungeon.level.distance(pos, sapperSpawns[i]) <= 16) + || Dungeon.level.distance(pos, sapperSpawns[i]) < Dungeon.level.distance(pos, closestSapperPos)) { + closestSapperPos = sapperSpawns[i]; + closestisAlive = sapperAlive; } } - //TODO get blink position int dashPos = closestSapperPos; //if spawn pos is more than 12 tiles away, get as close as possible @@ -303,7 +300,17 @@ public class GnollGeomancer extends Mob { dashPos = path.path.get(12); } - //TODO move to adjacent cell if pos is occupied + if (Actor.findChar(dashPos) != null){ + ArrayList candidates = new ArrayList<>(); + for (int i : PathFinder.NEIGHBOURS8){ + if (Actor.findChar(dashPos+i) == null && Dungeon.level.traps.get(dashPos+i) == null){ + candidates.add(dashPos+i); + } + } + if (!candidates.isEmpty()) { + dashPos = Random.element(candidates); + } + } for (int i = 0; i < sapperSpawns.length; i++){ if (sapperSpawns[i] == closestSapperPos){ @@ -311,12 +318,51 @@ public class GnollGeomancer extends Mob { } } - //TODO if geomancer alive, steal guard and set properties + if (closestisAlive){ + GnollSapper closest = null; + for (Mob m : Dungeon.level.mobs){ + if (m instanceof GnollSapper && ((GnollSapper) m).spawnPos == closestSapperPos){ + closest = (GnollSapper) m; + break; + } + } + if (closest != null){ + closest.linkPartner(this); + //moves sapper toward geomancer if it is too far away + if (Dungeon.level.distance(closest.pos, dashPos) > 3){ + int newSapperPos = new Ballistica(dashPos, closest.pos, Ballistica.STOP_TARGET).path.get(1); + ScrollOfTeleportation.appear(closest, newSapperPos); + closest.spawnPos = newSapperPos; + } + } + } return dashPos; } + public void linkSapper( GnollSapper sapper ){ + this.sapperID = sapper.id(); + if (sprite instanceof GnollGeomancerSprite){ + ((GnollGeomancerSprite) sprite).setupArmor(); + } + } + + public boolean hasSapper(){ + return sapperID != -1 + && Actor.findById(sapperID) instanceof GnollSapper + && ((GnollSapper)Actor.findById(sapperID)).isAlive(); + } + + public void loseSapper(){ + if (sapperID != -1){ + sapperID = -1; + if (sprite instanceof GnollGeomancerSprite){ + ((GnollGeomancerSprite) sprite).loseArmor(); + } + } + } + private void carveRock(int target){ Ballistica path = new Ballistica(pos, target, Ballistica.STOP_TARGET); @@ -410,9 +456,7 @@ public class GnollGeomancer extends Mob { return true; } else { enemySeen = true; - //sprite.showStatus(CharSprite.DEFAULT, "seen"); - //TODO cooldown if (abilityCooldown-- <= 0){ //do we care? boolean targetNextToBarricade = false; @@ -634,6 +678,7 @@ public class GnollGeomancer extends Mob { private static final String ROCK_FROM_POS = "rock_from_pos"; private static final String ROCK_TO_POS = "rock_to_pos"; + private static final String SAPPER_ID = "sapper_id"; private static final String SAPPER_SPAWNS = "sapper_spawns"; @Override @@ -644,6 +689,7 @@ public class GnollGeomancer extends Mob { bundle.put(ROCK_FROM_POS, throwingRockFromPos); bundle.put(ROCK_TO_POS, throwingRockToPos); + bundle.put(SAPPER_ID, sapperID); if (sapperSpawns != null){ bundle.put(SAPPER_SPAWNS, sapperSpawns); } @@ -657,6 +703,7 @@ public class GnollGeomancer extends Mob { throwingRockFromPos = bundle.getInt(ROCK_FROM_POS); throwingRockToPos = bundle.getInt(ROCK_TO_POS); + sapperID = bundle.getInt(SAPPER_ID); if (bundle.contains(SAPPER_SPAWNS)) { sapperSpawns = bundle.getIntArray(SAPPER_SPAWNS); } diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/GnollSapper.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/GnollSapper.java index 506f0bd6a..6882e9dd8 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/GnollSapper.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/GnollSapper.java @@ -55,24 +55,38 @@ public class GnollSapper extends Mob { } public int spawnPos; - private int guardID = -1; + private int partnerID = -1; private int abilityCooldown = Random.NormalIntRange(4, 6); private int throwingRockFromPos = -1; private int throwingRockToPos = -1; - public void linkGuard(GnollGuard g){ - guardID = g.id(); - g.linkSapper(this); + public void linkPartner(Char c){ + losePartner(); + partnerID = c.id(); + if (c instanceof GnollGuard) { + ((GnollGuard) c).linkSapper(this); + } else if (c instanceof GnollGeomancer){ + ((GnollGeomancer) c).linkSapper(this); + } + } + + public void losePartner(){ + if (partnerID != -1){ + if (Actor.findById(partnerID) instanceof GnollGuard) { + ((GnollGuard) Actor.findById(partnerID)).loseSapper(); + } else if (Actor.findById(partnerID) instanceof GnollGeomancer) { + ((GnollGeomancer) Actor.findById(partnerID)).loseSapper(); + } + partnerID = -1; + } } @Override public void die(Object cause) { super.die(cause); - if (guardID != -1 && Actor.findById(guardID) instanceof GnollGuard){ - ((GnollGuard) Actor.findById(guardID)).loseSapper(); - } + losePartner(); } @Override @@ -124,10 +138,10 @@ public class GnollSapper extends Mob { } else { enemySeen = true; - if (Actor.findById(guardID) instanceof GnollGuard + if (Actor.findById(partnerID) != null && Dungeon.level.distance(pos, enemy.pos) <= 3){ - ((GnollGuard) Actor.findById(guardID)).target = enemy.pos; - ((GnollGuard) Actor.findById(guardID)).aggro(enemy); + ((Mob) Actor.findById(partnerID)).target = enemy.pos; + ((Mob) Actor.findById(partnerID)).aggro(enemy); } if (abilityCooldown-- <= 0){ @@ -185,7 +199,7 @@ public class GnollSapper extends Mob { } private static final String SPAWN_POS = "spawn_pos"; - private static final String GUARD_ID = "guard_id"; + private static final String PARTNER_ID = "partner_id"; private static final String ABILITY_COOLDOWN = "ability_cooldown"; private static final String ROCK_FROM_POS = "rock_from_pos"; @@ -194,7 +208,7 @@ public class GnollSapper extends Mob { @Override public void storeInBundle(Bundle bundle) { super.storeInBundle(bundle); - bundle.put(GUARD_ID, guardID); + bundle.put(PARTNER_ID, partnerID); bundle.put(SPAWN_POS, spawnPos); bundle.put(ABILITY_COOLDOWN, abilityCooldown); bundle.put(ROCK_FROM_POS, throwingRockFromPos); @@ -204,7 +218,7 @@ public class GnollSapper extends Mob { @Override public void restoreFromBundle(Bundle bundle) { super.restoreFromBundle(bundle); - guardID = bundle.getInt( GUARD_ID ); + partnerID = bundle.getInt(PARTNER_ID); spawnPos = bundle.getInt(SPAWN_POS); abilityCooldown = bundle.getInt(ABILITY_COOLDOWN); throwingRockFromPos = bundle.getInt(ROCK_FROM_POS); diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/effects/particles/EarthParticle.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/effects/particles/EarthParticle.java index 2d5eaa997..a74165235 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/effects/particles/EarthParticle.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/effects/particles/EarthParticle.java @@ -65,6 +65,10 @@ public class EarthParticle extends PixelParticle { left = lifespan = 0.5f; size = 16; + + acc.y = 0; + speed.y = 0; + angularSpeed = 0; } public void resetSmall( float x, float y ) { diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/rooms/quest/MineLargeRoom.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/rooms/quest/MineLargeRoom.java index fccf9b248..42602cf1a 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/rooms/quest/MineLargeRoom.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/rooms/quest/MineLargeRoom.java @@ -137,7 +137,7 @@ public class MineLargeRoom extends CaveRoom { GnollGuard g = new GnollGuard(); g.pos = guardPos; level.mobs.add(g); - s.linkGuard(g); + s.linkPartner(g); int barricades = Random.Int(2) == 0 ? 2 : 1; for (int i = 0; i < barricades; i ++){ diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/traps/GnollRockfallTrap.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/traps/GnollRockfallTrap.java index a995f0d46..1255d92ce 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/traps/GnollRockfallTrap.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/traps/GnollRockfallTrap.java @@ -27,6 +27,7 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.Actor; import com.shatteredpixel.shatteredpixeldungeon.actors.Char; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Paralysis; +import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.GnollGeomancer; import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.GnollGuard; import com.shatteredpixel.shatteredpixeldungeon.effects.CellEmitter; import com.shatteredpixel.shatteredpixeldungeon.effects.Speck; @@ -78,7 +79,7 @@ public class GnollRockfallTrap extends RockfallTrap { Char ch = Actor.findChar( cell ); - if (ch != null && ch.isAlive()){ + if (ch != null && ch.isAlive() && !(ch instanceof GnollGeomancer)){ //5-10 less damage than normal rockfall traps int damage = Random.NormalIntRange(scalingDepth(), scalingDepth()*2); damage -= ch.drRoll(); @@ -91,7 +92,8 @@ public class GnollRockfallTrap extends RockfallTrap { Dungeon.fail( this ); GLog.n( Messages.get(this, "ondeath") ); } - } else if (Dungeon.level instanceof MiningLevel + } else if (ch == null + && Dungeon.level instanceof MiningLevel && Dungeon.level.traps.get(cell) == null && Dungeon.level.plants.get(cell) == null && Random.Int(2) == 0){ diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/GnollGeomancerSprite.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/GnollGeomancerSprite.java index ebd39f019..76f923ccf 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/GnollGeomancerSprite.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/GnollGeomancerSprite.java @@ -22,14 +22,19 @@ package com.shatteredpixel.shatteredpixeldungeon.sprites; import com.shatteredpixel.shatteredpixeldungeon.Assets; +import com.shatteredpixel.shatteredpixeldungeon.actors.Char; import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.GnollGeomancer; +import com.shatteredpixel.shatteredpixeldungeon.effects.particles.EarthParticle; import com.watabou.noosa.TextureFilm; +import com.watabou.noosa.particles.Emitter; import com.watabou.utils.ColorMath; public class GnollGeomancerSprite extends MobSprite { private Animation statue; + private Emitter earthArmor; + public GnollGeomancerSprite() { super(); @@ -59,6 +64,62 @@ public class GnollGeomancerSprite extends MobSprite { scale.set(1.25f); } + @Override + public void link( Char ch ) { + super.link( ch ); + + if (ch instanceof GnollGeomancer && ((GnollGeomancer) ch).hasSapper()){ + setupArmor(); + } + if (ch != null && ch.buff(GnollGeomancer.RockArmor.class) != null){ + play( statue ); + } + } + + public void setupArmor(){ + if (earthArmor == null) { + earthArmor = emitter(); + earthArmor.fillTarget = false; + earthArmor.y = height()/2f; + earthArmor.x = (2*scale.x); + earthArmor.width = width()-(4*scale.x); + earthArmor.height = height() - (10*scale.y); + earthArmor.pour(EarthParticle.SMALL, 0.15f); + } + } + + public void loseArmor(){ + if (earthArmor != null){ + earthArmor.on = false; + earthArmor = null; + } + } + + @Override + public void update() { + super.update(); + + if (earthArmor != null){ + earthArmor.visible = visible; + } + } + + @Override + public void die() { + super.die(); + if (earthArmor != null){ + earthArmor.on = false; + } + } + + @Override + public void kill() { + super.kill(); + if (earthArmor != null){ + earthArmor.killAndErase(); + } + } + @Override public void onComplete( Animation anim ) { if (anim == zap) { diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/GnollGuardSprite.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/GnollGuardSprite.java index 87d2eea26..6b452823f 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/GnollGuardSprite.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/GnollGuardSprite.java @@ -68,9 +68,9 @@ public class GnollGuardSprite extends MobSprite { earthArmor = emitter(); earthArmor.fillTarget = false; earthArmor.y = height()/2f; - earthArmor.x = 2; - earthArmor.width = width()-4; - earthArmor.height = height() - 10f; + earthArmor.x = (2*scale.x); + earthArmor.width = width()-(4*scale.x); + earthArmor.height = height() - (10*scale.y); earthArmor.pour(EarthParticle.SMALL, 0.15f); } }