v2.2.0: overhauled retreating enemy AI
This commit is contained in:
@@ -31,6 +31,7 @@ public class PathFinder {
|
|||||||
|
|
||||||
private static boolean[] goals;
|
private static boolean[] goals;
|
||||||
private static int[] queue;
|
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 size = 0;
|
||||||
private static int width = 0;
|
private static int width = 0;
|
||||||
@@ -57,6 +58,7 @@ public class PathFinder {
|
|||||||
distance = new int[size];
|
distance = new int[size];
|
||||||
goals = new boolean[size];
|
goals = new boolean[size];
|
||||||
queue = new int[size];
|
queue = new int[size];
|
||||||
|
queued = new boolean[size];
|
||||||
|
|
||||||
maxVal = new int[size];
|
maxVal = new int[size];
|
||||||
Arrays.fill(maxVal, Integer.MAX_VALUE);
|
Arrays.fill(maxVal, Integer.MAX_VALUE);
|
||||||
@@ -127,9 +129,51 @@ public class PathFinder {
|
|||||||
return best;
|
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++) {
|
for (int i=0; i < size; i++) {
|
||||||
goals[i] = distance[i] == d;
|
goals[i] = distance[i] == d;
|
||||||
}
|
}
|
||||||
@@ -284,7 +328,9 @@ public class PathFinder {
|
|||||||
|
|
||||||
return pathFound;
|
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 ) {
|
private static int buildEscapeDistanceMap( int cur, int from, int lookAhead, boolean[] passable ) {
|
||||||
|
|
||||||
System.arraycopy(maxVal, 0, distance, 0, maxVal.length);
|
System.arraycopy(maxVal, 0, distance, 0, maxVal.length);
|
||||||
|
|||||||
@@ -26,10 +26,12 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.Char;
|
|||||||
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Amok;
|
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Amok;
|
||||||
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.AscensionChallenge;
|
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.AscensionChallenge;
|
||||||
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Awareness;
|
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.Light;
|
||||||
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.MagicalSight;
|
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.MagicalSight;
|
||||||
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.MindVision;
|
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.MindVision;
|
||||||
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.RevealedArea;
|
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.Hero;
|
||||||
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Talent;
|
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Talent;
|
||||||
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.huntress.SpiritHawk;
|
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 ) {
|
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);
|
boolean[] passable = findPassable(ch, pass, visible, false, true);
|
||||||
passable[ch.pos] = true;
|
passable[ch.pos] = true;
|
||||||
|
|
||||||
int step = PathFinder.getStepBack( ch.pos, from, passable );
|
//only consider other chars impassable if our retreat step may collide with them
|
||||||
while (step != -1 && Actor.findChar(step) != null && chars){
|
if (chars) {
|
||||||
passable[step] = false;
|
for (Char c : Actor.chars()) {
|
||||||
step = PathFinder.getStepBack( ch.pos, from, passable );
|
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 );
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,11 +25,8 @@ import com.shatteredpixel.shatteredpixeldungeon.Assets;
|
|||||||
import com.shatteredpixel.shatteredpixeldungeon.Dungeon;
|
import com.shatteredpixel.shatteredpixeldungeon.Dungeon;
|
||||||
import com.shatteredpixel.shatteredpixeldungeon.actors.Actor;
|
import com.shatteredpixel.shatteredpixeldungeon.actors.Actor;
|
||||||
import com.shatteredpixel.shatteredpixeldungeon.actors.Char;
|
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.Buff;
|
||||||
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Dread;
|
|
||||||
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Haste;
|
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.actors.hero.Hero;
|
||||||
import com.shatteredpixel.shatteredpixeldungeon.effects.CellEmitter;
|
import com.shatteredpixel.shatteredpixeldungeon.effects.CellEmitter;
|
||||||
import com.shatteredpixel.shatteredpixeldungeon.effects.Speck;
|
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.scrolls.ScrollOfTeleportation;
|
||||||
import com.shatteredpixel.shatteredpixeldungeon.items.wands.Wand;
|
import com.shatteredpixel.shatteredpixeldungeon.items.wands.Wand;
|
||||||
import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
|
import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
|
||||||
import com.shatteredpixel.shatteredpixeldungeon.sprites.CharSprite;
|
|
||||||
import com.shatteredpixel.shatteredpixeldungeon.sprites.MimicSprite;
|
import com.shatteredpixel.shatteredpixeldungeon.sprites.MimicSprite;
|
||||||
import com.shatteredpixel.shatteredpixeldungeon.utils.GLog;
|
import com.shatteredpixel.shatteredpixeldungeon.utils.GLog;
|
||||||
import com.watabou.noosa.audio.Sample;
|
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
|
@Override
|
||||||
protected void nowhereToRun() {
|
protected void nowhereToRun() {
|
||||||
if (buff( Terror.class ) == null
|
super.nowhereToRun();
|
||||||
&& buffs( AllyBuff.class ).isEmpty()
|
if (state == HUNTING){
|
||||||
&& buff( Dread.class ) == null) {
|
spend(-TICK); //crystal mimics are fast!
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
protected class Fleeing implements AiState {
|
||||||
|
|
||||||
public static final String TAG = "FLEEING";
|
public static final String TAG = "FLEEING";
|
||||||
@@ -1159,9 +1158,12 @@ public abstract class Mob extends Char {
|
|||||||
@Override
|
@Override
|
||||||
public boolean act( boolean enemyInFOV, boolean justAlerted ) {
|
public boolean act( boolean enemyInFOV, boolean justAlerted ) {
|
||||||
enemySeen = enemyInFOV;
|
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){
|
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.
|
//if enemy isn't in FOV, keep running from their previous position.
|
||||||
} else if (enemyInFOV) {
|
} 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() {
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -266,13 +266,5 @@ public class Spinner extends Mob {
|
|||||||
return super.act(enemyInFOV, justAlerted);
|
return super.act(enemyInFOV, justAlerted);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void nowhereToRun() {
|
|
||||||
if (buff(Terror.class) == null && buff(Dread.class) == null) {
|
|
||||||
state = HUNTING;
|
|
||||||
} else {
|
|
||||||
super.nowhereToRun();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,9 +23,6 @@ package com.shatteredpixel.shatteredpixeldungeon.actors.mobs;
|
|||||||
|
|
||||||
import com.shatteredpixel.shatteredpixeldungeon.Dungeon;
|
import com.shatteredpixel.shatteredpixeldungeon.Dungeon;
|
||||||
import com.shatteredpixel.shatteredpixeldungeon.actors.Char;
|
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.actors.hero.Hero;
|
||||||
import com.shatteredpixel.shatteredpixeldungeon.effects.CellEmitter;
|
import com.shatteredpixel.shatteredpixeldungeon.effects.CellEmitter;
|
||||||
import com.shatteredpixel.shatteredpixeldungeon.effects.Speck;
|
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.Honeypot;
|
||||||
import com.shatteredpixel.shatteredpixeldungeon.items.Item;
|
import com.shatteredpixel.shatteredpixeldungeon.items.Item;
|
||||||
import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
|
import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
|
||||||
import com.shatteredpixel.shatteredpixeldungeon.sprites.CharSprite;
|
|
||||||
import com.shatteredpixel.shatteredpixeldungeon.sprites.ThiefSprite;
|
import com.shatteredpixel.shatteredpixeldungeon.sprites.ThiefSprite;
|
||||||
import com.shatteredpixel.shatteredpixeldungeon.utils.GLog;
|
import com.shatteredpixel.shatteredpixeldungeon.utils.GLog;
|
||||||
import com.watabou.utils.Bundle;
|
import com.watabou.utils.Bundle;
|
||||||
@@ -200,44 +196,34 @@ public class Thief extends Mob {
|
|||||||
|
|
||||||
private class Fleeing extends Mob.Fleeing {
|
private class Fleeing extends Mob.Fleeing {
|
||||||
@Override
|
@Override
|
||||||
protected void nowhereToRun() {
|
protected void escaped() {
|
||||||
if (buff( Terror.class ) == null
|
if (item != null
|
||||||
&& buff( Dread.class ) == null
|
&& !Dungeon.level.heroFOV[pos]
|
||||||
&& buffs( AllyBuff.class ).isEmpty() ) {
|
&& Dungeon.level.distance(Dungeon.hero.pos, pos) >= 6) {
|
||||||
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);
|
|
||||||
|
|
||||||
|
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 {
|
} else {
|
||||||
super.nowhereToRun();
|
state = WANDERING;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user