v2.3.0: implemented gnoll geomancer's interaction with living sappers

This commit is contained in:
Evan Debenham
2023-11-29 16:31:16 -05:00
parent 3694bc666d
commit c3c4cc9f54
7 changed files with 165 additions and 37 deletions

View File

@@ -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<Integer> 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);
}

View File

@@ -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);

View File

@@ -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 ) {

View File

@@ -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 ++){

View File

@@ -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){

View File

@@ -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) {

View File

@@ -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);
}
}