diff --git a/SPD-classes/src/main/java/com/watabou/utils/PathFinder.java b/SPD-classes/src/main/java/com/watabou/utils/PathFinder.java index d08858f5d..28a13c82a 100644 --- a/SPD-classes/src/main/java/com/watabou/utils/PathFinder.java +++ b/SPD-classes/src/main/java/com/watabou/utils/PathFinder.java @@ -31,6 +31,7 @@ public class PathFinder { private static boolean[] goals; private static int[] queue; + private static boolean[] queued; //currently only used in getStepBack, other can piggyback on distance private static int size = 0; private static int width = 0; @@ -57,6 +58,7 @@ public class PathFinder { distance = new int[size]; goals = new boolean[size]; queue = new int[size]; + queued = new boolean[size]; maxVal = new int[size]; Arrays.fill(maxVal, Integer.MAX_VALUE); @@ -127,9 +129,51 @@ public class PathFinder { return best; } - public static int getStepBack( int cur, int from, boolean[] passable ) { + public static int getStepBack( int cur, int from, int lookahead, boolean[] passable, boolean canApproachFromPos ) { + + int d = buildEscapeDistanceMap( cur, from, lookahead, passable ); + if (d == 0) return -1; + + if (!canApproachFromPos) { + //We can't approach the position we are retreating from + //re-calculate based on this, and reduce the target distance if need-be + int head = 0; + int tail = 0; + + int newD = distance[cur]; + BArray.setFalse(queued); + + queue[tail++] = cur; + queued[cur] = true; + + while (head < tail) { + int step = queue[head++]; + + if (distance[step] > newD) { + newD = distance[step]; + } + + int start = (step % width == 0 ? 3 : 0); + int end = ((step + 1) % width == 0 ? 3 : 0); + for (int i = start; i < dirLR.length - end; i++) { + + int n = step + dirLR[i]; + if (n >= 0 && n < size && passable[n]) { + if (distance[n] < distance[cur]) { + passable[n] = false; + } else if (distance[n] >= distance[step] && !queued[n]) { + // Add to queue + queue[tail++] = n; + queued[n] = true; + } + } + } + + } + + d = Math.min(newD, d); + } - int d = buildEscapeDistanceMap( cur, from, 5, passable ); for (int i=0; i < size; i++) { goals[i] = distance[i] == d; } @@ -284,7 +328,9 @@ public class PathFinder { return pathFound; } - + + //the lookahead is the target number of cells to retreat toward from our current position's + // distance from the position we are escaping from. Returns the highest found distance, up to the lookahead private static int buildEscapeDistanceMap( int cur, int from, int lookAhead, boolean[] passable ) { System.arraycopy(maxVal, 0, distance, 0, maxVal.length); diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Dungeon.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Dungeon.java index 08dd9940d..a03a0c6ad 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Dungeon.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Dungeon.java @@ -26,10 +26,12 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.Char; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Amok; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.AscensionChallenge; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Awareness; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Dread; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Light; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.MagicalSight; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.MindVision; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.RevealedArea; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Terror; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Talent; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.huntress.SpiritHawk; @@ -1013,16 +1015,21 @@ public class Dungeon { } public static int flee( Char ch, int from, boolean[] pass, boolean[] visible, boolean chars ) { - //only consider chars impassable if our retreat path runs into them boolean[] passable = findPassable(ch, pass, visible, false, true); passable[ch.pos] = true; - int step = PathFinder.getStepBack( ch.pos, from, passable ); - while (step != -1 && Actor.findChar(step) != null && chars){ - passable[step] = false; - step = PathFinder.getStepBack( ch.pos, from, passable ); + //only consider other chars impassable if our retreat step may collide with them + if (chars) { + for (Char c : Actor.chars()) { + if (c.pos == from || Dungeon.level.adjacent(c.pos, ch.pos)) { + passable[c.pos] = false; + } + } } - return step; + + //chars affected by terror have a shorter lookahead and can't approach the fear source + boolean canApproachFromPos = ch.buff(Terror.class) == null && ch.buff(Dread.class) == null; + return PathFinder.getStepBack( ch.pos, from, canApproachFromPos ? 8 : 4, passable, canApproachFromPos ); } diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/CrystalMimic.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/CrystalMimic.java index 11d196391..c7690efdf 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/CrystalMimic.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/CrystalMimic.java @@ -25,11 +25,8 @@ import com.shatteredpixel.shatteredpixeldungeon.Assets; import com.shatteredpixel.shatteredpixeldungeon.Dungeon; import com.shatteredpixel.shatteredpixeldungeon.actors.Actor; import com.shatteredpixel.shatteredpixeldungeon.actors.Char; -import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.AllyBuff; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff; -import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Dread; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Haste; -import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Terror; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero; import com.shatteredpixel.shatteredpixeldungeon.effects.CellEmitter; import com.shatteredpixel.shatteredpixeldungeon.effects.Speck; @@ -41,7 +38,6 @@ import com.shatteredpixel.shatteredpixeldungeon.items.rings.Ring; import com.shatteredpixel.shatteredpixeldungeon.items.scrolls.ScrollOfTeleportation; import com.shatteredpixel.shatteredpixeldungeon.items.wands.Wand; import com.shatteredpixel.shatteredpixeldungeon.messages.Messages; -import com.shatteredpixel.shatteredpixeldungeon.sprites.CharSprite; import com.shatteredpixel.shatteredpixeldungeon.sprites.MimicSprite; import com.shatteredpixel.shatteredpixeldungeon.utils.GLog; import com.watabou.noosa.audio.Sample; @@ -182,25 +178,23 @@ public class CrystalMimic extends Mimic { } } - private class Fleeing extends Mob.Fleeing{ + private class Fleeing extends Mob.Fleeing { + @Override + protected void escaped() { + if (!Dungeon.level.heroFOV[pos] && Dungeon.level.distance(Dungeon.hero.pos, pos) >= 6) { + GLog.n(Messages.get(CrystalMimic.class, "escaped")); + destroy(); + sprite.killAndErase(); + } else { + state = WANDERING; + } + } + @Override protected void nowhereToRun() { - if (buff( Terror.class ) == null - && buffs( AllyBuff.class ).isEmpty() - && buff( Dread.class ) == null) { - if (enemySeen) { - sprite.showStatus(CharSprite.NEGATIVE, Messages.get(Mob.class, "rage")); - state = HUNTING; - } else if (!Dungeon.level.heroFOV[pos] && Dungeon.level.distance(Dungeon.hero.pos, pos) >= 6) { - GLog.n( Messages.get(CrystalMimic.class, "escaped")); - if (Dungeon.level.heroFOV[pos]) CellEmitter.get(pos).burst(Speck.factory(Speck.WOOL), 6); - destroy(); - sprite.killAndErase(); - } else { - state = WANDERING; - } - } else { - super.nowhereToRun(); + super.nowhereToRun(); + if (state == HUNTING){ + spend(-TICK); //crystal mimics are fast! } } } diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/Mob.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/Mob.java index 12f399826..fb8b470bf 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/Mob.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/Mob.java @@ -1151,7 +1151,6 @@ public abstract class Mob extends Char { } } - //FIXME this works fairly well but is coded poorly. Should refactor protected class Fleeing implements AiState { public static final String TAG = "FLEEING"; @@ -1159,9 +1158,12 @@ public abstract class Mob extends Char { @Override public boolean act( boolean enemyInFOV, boolean justAlerted ) { enemySeen = enemyInFOV; - //loses target when 0-dist rolls a 6 or greater. + //triggers escape logic when 0-dist rolls a 6 or greater. if (enemy == null || !enemyInFOV && 1 + Random.Int(Dungeon.level.distance(pos, target)) >= 6){ - target = -1; + escaped(); + if (state != FLEEING){ + return true; + } //if enemy isn't in FOV, keep running from their previous position. } else if (enemyInFOV) { @@ -1183,7 +1185,22 @@ public abstract class Mob extends Char { } } + protected void escaped(){ + //does nothing by default, some enemies have special logic for this + } + + //enemies will turn and fight if they have nowhere to run and aren't affect by terror protected void nowhereToRun() { + if (buff( Terror.class ) == null + && buffs( AllyBuff.class ).isEmpty() + && buff( Dread.class ) == null) { + if (enemySeen) { + sprite.showStatus(CharSprite.NEGATIVE, Messages.get(Mob.class, "rage")); + state = HUNTING; + } else { + state = WANDERING; + } + } } } 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 067cacb64..49bf6ae56 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 @@ -266,13 +266,5 @@ public class Spinner extends Mob { return super.act(enemyInFOV, justAlerted); } - @Override - protected void nowhereToRun() { - if (buff(Terror.class) == null && buff(Dread.class) == null) { - state = HUNTING; - } else { - super.nowhereToRun(); - } - } } } diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/Thief.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/Thief.java index 5c56c5cb9..a71bfe32f 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/Thief.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/Thief.java @@ -23,9 +23,6 @@ package com.shatteredpixel.shatteredpixeldungeon.actors.mobs; import com.shatteredpixel.shatteredpixeldungeon.Dungeon; import com.shatteredpixel.shatteredpixeldungeon.actors.Char; -import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.AllyBuff; -import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Dread; -import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Terror; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero; import com.shatteredpixel.shatteredpixeldungeon.effects.CellEmitter; import com.shatteredpixel.shatteredpixeldungeon.effects.Speck; @@ -34,7 +31,6 @@ import com.shatteredpixel.shatteredpixeldungeon.items.Gold; import com.shatteredpixel.shatteredpixeldungeon.items.Honeypot; import com.shatteredpixel.shatteredpixeldungeon.items.Item; import com.shatteredpixel.shatteredpixeldungeon.messages.Messages; -import com.shatteredpixel.shatteredpixeldungeon.sprites.CharSprite; import com.shatteredpixel.shatteredpixeldungeon.sprites.ThiefSprite; import com.shatteredpixel.shatteredpixeldungeon.utils.GLog; import com.watabou.utils.Bundle; @@ -200,44 +196,34 @@ public class Thief extends Mob { private class Fleeing extends Mob.Fleeing { @Override - protected void nowhereToRun() { - if (buff( Terror.class ) == null - && buff( Dread.class ) == null - && buffs( AllyBuff.class ).isEmpty() ) { - if (enemySeen) { - sprite.showStatus(CharSprite.NEGATIVE, Messages.get(Mob.class, "rage")); - state = HUNTING; - } else if (item != null - && !Dungeon.level.heroFOV[pos] - && Dungeon.level.distance(Dungeon.hero.pos, pos) >= 6) { - - int count = 32; - int newPos; - do { - newPos = Dungeon.level.randomRespawnCell( Thief.this ); - if (count-- <= 0) { - break; - } - } while (newPos == -1 || Dungeon.level.heroFOV[newPos] || Dungeon.level.distance(newPos, pos) < (count/3)); - - if (newPos != -1) { - - if (Dungeon.level.heroFOV[pos]) CellEmitter.get(pos).burst(Speck.factory(Speck.WOOL), 6); - pos = newPos; - sprite.place( pos ); - sprite.visible = Dungeon.level.heroFOV[pos]; - if (Dungeon.level.heroFOV[pos]) CellEmitter.get(pos).burst(Speck.factory(Speck.WOOL), 6); + protected void escaped() { + if (item != null + && !Dungeon.level.heroFOV[pos] + && Dungeon.level.distance(Dungeon.hero.pos, pos) >= 6) { + int count = 32; + int newPos; + do { + newPos = Dungeon.level.randomRespawnCell( Thief.this ); + if (count-- <= 0) { + break; } + } while (newPos == -1 || Dungeon.level.heroFOV[newPos] || Dungeon.level.distance(newPos, pos) < (count/3)); + + if (newPos != -1) { + + pos = newPos; + sprite.place( pos ); + sprite.visible = Dungeon.level.heroFOV[pos]; + if (Dungeon.level.heroFOV[pos]) CellEmitter.get(pos).burst(Speck.factory(Speck.WOOL), 6); - if (item != null) GLog.n( Messages.get(Thief.class, "escapes", item.name())); - item = null; - state = WANDERING; - } else { - state = WANDERING; } + + if (item != null) GLog.n( Messages.get(Thief.class, "escapes", item.name())); + item = null; + state = WANDERING; } else { - super.nowhereToRun(); + state = WANDERING; } } }