v2.2.0: overhauled retreating enemy AI

This commit is contained in:
Evan Debenham
2023-07-19 15:22:16 -04:00
parent c9eb216d20
commit 8abc3699d5
6 changed files with 120 additions and 78 deletions
@@ -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 );
}
@@ -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!
}
}
}
@@ -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;
}
}
}
}
@@ -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();
}
}
}
}
@@ -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;
}
}
}