v0.8.0: AI improvements (primarily pathfinding):
- removed path lookaheads, characters now only check the next tile for validity - the hero is now interrupted if their path is broken and replaced by a much longer one - added pathfinding functionality to ignore characters - significantly improved hunting enemy pathfinding when a route is blocked - improved enemy logic for switching targets if current one is unreachable
This commit is contained in:
parent
5206141aeb
commit
0653d0d1f2
|
@ -808,7 +808,7 @@ public class Dungeon {
|
|||
BArray.setFalse(passable);
|
||||
}
|
||||
|
||||
public static PathFinder.Path findPath(Char ch, int from, int to, boolean pass[], boolean[] visible ) {
|
||||
public static PathFinder.Path findPath(Char ch, int to, boolean[] pass, boolean[] vis, boolean chars) {
|
||||
|
||||
setupPassable();
|
||||
if (ch.flying || ch.buff( Amok.class ) != null) {
|
||||
|
@ -821,19 +821,21 @@ public class Dungeon {
|
|||
BArray.and( pass, Dungeon.level.openSpace, passable );
|
||||
}
|
||||
|
||||
if (chars) {
|
||||
for (Char c : Actor.chars()) {
|
||||
if (visible[c.pos]) {
|
||||
if (vis[c.pos]) {
|
||||
passable[c.pos] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return PathFinder.find( from, to, passable );
|
||||
return PathFinder.find( ch.pos, to, passable );
|
||||
|
||||
}
|
||||
|
||||
public static int findStep(Char ch, int from, int to, boolean pass[], boolean[] visible ) {
|
||||
public static int findStep(Char ch, int to, boolean[] pass, boolean[] visible, boolean chars ) {
|
||||
|
||||
if (Dungeon.level.adjacent( from, to )) {
|
||||
if (Dungeon.level.adjacent( ch.pos, to )) {
|
||||
return Actor.findChar( to ) == null && (pass[to] || Dungeon.level.avoid[to]) ? to : -1;
|
||||
}
|
||||
|
||||
|
@ -848,17 +850,19 @@ public class Dungeon {
|
|||
BArray.and( pass, Dungeon.level.openSpace, passable );
|
||||
}
|
||||
|
||||
if (chars){
|
||||
for (Char c : Actor.chars()) {
|
||||
if (visible[c.pos]) {
|
||||
passable[c.pos] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return PathFinder.getStep( from, to, passable );
|
||||
return PathFinder.getStep( ch.pos, to, passable );
|
||||
|
||||
}
|
||||
|
||||
public static int flee( Char ch, int cur, int from, boolean pass[], boolean[] visible ) {
|
||||
public static int flee( Char ch, int from, boolean[] pass, boolean[] visible, boolean chars ) {
|
||||
|
||||
setupPassable();
|
||||
if (ch.flying) {
|
||||
|
@ -871,14 +875,16 @@ public class Dungeon {
|
|||
BArray.and( pass, Dungeon.level.openSpace, passable );
|
||||
}
|
||||
|
||||
if (chars) {
|
||||
for (Char c : Actor.chars()) {
|
||||
if (visible[c.pos]) {
|
||||
passable[c.pos] = false;
|
||||
}
|
||||
}
|
||||
passable[cur] = true;
|
||||
}
|
||||
passable[ch.pos] = true;
|
||||
|
||||
return PathFinder.getStepBack( cur, from, passable );
|
||||
return PathFinder.getStepBack( ch.pos, from, passable );
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -1156,15 +1156,8 @@ public class Hero extends Char {
|
|||
else if (path.getLast() != target)
|
||||
newPath = true;
|
||||
else {
|
||||
//looks ahead for path validity, up to length-1 or 2.
|
||||
//Note that this is shorter than for mobs, so that mobs usually yield to the hero
|
||||
int lookAhead = (int) GameMath.gate(0, path.size()-1, 2);
|
||||
for (int i = 0; i < lookAhead; i++){
|
||||
int cell = path.get(i);
|
||||
if (!Dungeon.level.passable[cell] || (fieldOfView[cell] && Actor.findChar(cell) != null)) {
|
||||
if (!Dungeon.level.passable[path.get(0)] || Actor.findChar(path.get(0)) != null) {
|
||||
newPath = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1179,7 +1172,12 @@ public class Hero extends Char {
|
|||
passable[i] = p[i] && (v[i] || m[i]);
|
||||
}
|
||||
|
||||
path = Dungeon.findPath(this, pos, target, passable, fieldOfView);
|
||||
PathFinder.Path newpath = Dungeon.findPath(this, target, passable, fieldOfView, true);
|
||||
if (newpath != null && path != null && newpath.size() > 2*path.size()){
|
||||
path = null;
|
||||
} else {
|
||||
path = newpath;
|
||||
}
|
||||
}
|
||||
|
||||
if (path == null) return false;
|
||||
|
|
|
@ -49,7 +49,6 @@ public class Ghoul extends Mob {
|
|||
|
||||
SLEEPING = new Sleeping();
|
||||
WANDERING = new Wandering();
|
||||
HUNTING = new Hunting();
|
||||
state = SLEEPING;
|
||||
|
||||
properties.add(Property.UNDEAD);
|
||||
|
@ -203,54 +202,6 @@ public class Ghoul extends Mob {
|
|||
}
|
||||
}
|
||||
|
||||
//TODO currently very similar to super.Hunting and is largely a stop-gap, need to refactor
|
||||
private class Hunting extends Mob.Hunting {
|
||||
|
||||
@Override
|
||||
public boolean act( boolean enemyInFOV, boolean justAlerted ) {
|
||||
enemySeen = enemyInFOV;
|
||||
if (enemyInFOV && !isCharmedBy( enemy ) && canAttack( enemy )) {
|
||||
|
||||
return doAttack( enemy );
|
||||
|
||||
} else {
|
||||
|
||||
if (enemyInFOV) {
|
||||
target = enemy.pos;
|
||||
} else if (enemy == null) {
|
||||
state = WANDERING;
|
||||
target = Dungeon.level.randomDestination( Ghoul.this );
|
||||
return true;
|
||||
}
|
||||
|
||||
int oldPos = pos;
|
||||
if (target != -1 && getCloser( target )) {
|
||||
|
||||
spend( 1 / speed() );
|
||||
return moveSprite( oldPos, pos );
|
||||
|
||||
} else {
|
||||
|
||||
Ghoul partner = (Ghoul) Actor.findById( partnerID );
|
||||
if (!enemyInFOV) {
|
||||
spend( TICK );
|
||||
sprite.showLost();
|
||||
state = WANDERING;
|
||||
target = Dungeon.level.randomDestination( Ghoul.this );
|
||||
|
||||
//try to move closer to partner if they can't move to hero
|
||||
} else if (partner != null && getCloser(partner.pos)) {
|
||||
spend( 1 / speed() );
|
||||
return moveSprite( oldPos, pos );
|
||||
} else {
|
||||
spend( TICK );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class GhoulLifeLink extends Buff{
|
||||
|
||||
private Ghoul ghoul;
|
||||
|
|
|
@ -383,7 +383,7 @@ public abstract class Mob extends Char {
|
|||
path.add(target);
|
||||
}
|
||||
|
||||
} else if (!path.isEmpty()) {
|
||||
} else {
|
||||
//if the new target is simply 1 earlier in the path shorten the path
|
||||
if (path.getLast() == target) {
|
||||
|
||||
|
@ -404,39 +404,65 @@ public abstract class Mob extends Char {
|
|||
|
||||
}
|
||||
|
||||
|
||||
//checks if the next cell along the current path can be stepped into
|
||||
if (!newPath) {
|
||||
//looks ahead for path validity, up to length-1 or 4, but always at least 1.
|
||||
int lookAhead = (int)GameMath.gate(1, path.size()-1, 4);
|
||||
for (int i = 0; i < lookAhead; i++) {
|
||||
int cell = path.get(i);
|
||||
if (!Dungeon.level.passable[cell]
|
||||
|| (!flying && Dungeon.level.avoid[target])
|
||||
|| (Char.hasProp(this, Char.Property.LARGE) && !Dungeon.level.openSpace[cell])
|
||||
|| (fieldOfView[cell] && Actor.findChar(cell) != null)) {
|
||||
int nextCell = path.removeFirst();
|
||||
if (!Dungeon.level.passable[nextCell]
|
||||
|| (!flying && Dungeon.level.avoid[nextCell])
|
||||
|| (Char.hasProp(this, Char.Property.LARGE) && !Dungeon.level.openSpace[nextCell])
|
||||
|| Actor.findChar(nextCell) != null) {
|
||||
|
||||
newPath = true;
|
||||
//If the next cell on the path can't be moved into, see if there is another cell that could replace it
|
||||
if (!path.isEmpty()) {
|
||||
for (int i : PathFinder.NEIGHBOURS8) {
|
||||
if (Dungeon.level.adjacent(pos, nextCell + i) && Dungeon.level.adjacent(nextCell + i, path.getFirst())) {
|
||||
if (Dungeon.level.passable[nextCell+i]
|
||||
&& (flying || !Dungeon.level.avoid[nextCell+i])
|
||||
&& (!Char.hasProp(this, Char.Property.LARGE) || Dungeon.level.openSpace[nextCell+i])
|
||||
&& Actor.findChar(nextCell+i) == null){
|
||||
path.addFirst(nextCell+i);
|
||||
newPath = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (newPath) {
|
||||
path = Dungeon.findPath(this, pos, target,
|
||||
Dungeon.level.passable,
|
||||
fieldOfView);
|
||||
}
|
||||
} else {
|
||||
path.addFirst(nextCell);
|
||||
}
|
||||
}
|
||||
|
||||
//if hunting something, don't follow a path that is extremely inefficient
|
||||
//FIXME this is fairly brittle, primarily it assumes that hunting mobs can't see through
|
||||
// permanent terrain, such that if their path is inefficient it's always because
|
||||
// of a temporary blockage, and therefore waiting for it to clear is the best option.
|
||||
if (path == null ||
|
||||
(state == HUNTING && path.size() > Math.max(9, 2*Dungeon.level.distance(pos, target)))) {
|
||||
//
|
||||
//generate a new path
|
||||
if (newPath) {
|
||||
//If we aren't hunting, always take a full path
|
||||
PathFinder.Path full = Dungeon.findPath(this, target, Dungeon.level.passable, fieldOfView, true);
|
||||
if (state != HUNTING){
|
||||
path = full;
|
||||
} else {
|
||||
//otherwise, check if other characters are forcing us to take a very slow route
|
||||
// and don't try to go around them yet in response, basically assume their blockage is temporary
|
||||
PathFinder.Path ignoreChars = Dungeon.findPath(this, target, Dungeon.level.passable, fieldOfView, false);
|
||||
if (full == null || full.size() > 2*ignoreChars.size()){
|
||||
//check if first cell of shorter path is valid. If it is, use new shorter path. Otherwise do nothing and wait.
|
||||
path = ignoreChars;
|
||||
if (!Dungeon.level.passable[ignoreChars.getFirst()]
|
||||
|| (!flying && Dungeon.level.avoid[ignoreChars.getFirst()])
|
||||
|| (Char.hasProp(this, Char.Property.LARGE) && !Dungeon.level.openSpace[ignoreChars.getFirst()])
|
||||
|| Actor.findChar(ignoreChars.getFirst()) != null) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
path = full;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (path != null) {
|
||||
step = path.removeFirst();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (step != -1) {
|
||||
move( step );
|
||||
|
@ -451,9 +477,7 @@ public abstract class Mob extends Char {
|
|||
return false;
|
||||
}
|
||||
|
||||
int step = Dungeon.flee( this, pos, target,
|
||||
Dungeon.level.passable,
|
||||
fieldOfView );
|
||||
int step = Dungeon.flee( this, target, Dungeon.level.passable, fieldOfView, true );
|
||||
if (step != -1) {
|
||||
move( step );
|
||||
return true;
|
||||
|
@ -845,6 +869,17 @@ public abstract class Mob extends Char {
|
|||
return moveSprite( oldPos, pos );
|
||||
|
||||
} else {
|
||||
|
||||
//if moving towards an enemy isn't possible, try to switch targets to another enemy that is closer
|
||||
Char oldEnemy = enemy;
|
||||
enemy = null;
|
||||
enemy = chooseEnemy();
|
||||
if (enemy != null && enemy != oldEnemy){
|
||||
return act(enemyInFOV, justAlerted);
|
||||
} else {
|
||||
enemy = oldEnemy;
|
||||
}
|
||||
|
||||
spend( TICK );
|
||||
if (!enemyInFOV) {
|
||||
sprite.showLost();
|
||||
|
|
|
@ -170,13 +170,13 @@ public class NewDM300 extends Mob {
|
|||
if (Dungeon.level.adjacent(pos, Dungeon.hero.pos)){
|
||||
canReach = true;
|
||||
} else {
|
||||
canReach = (Dungeon.findStep(this, pos, Dungeon.hero.pos, Dungeon.level.openSpace, fieldOfView) != -1);
|
||||
canReach = (Dungeon.findStep(this, Dungeon.hero.pos, Dungeon.level.openSpace, fieldOfView, true) != -1);
|
||||
}
|
||||
} else {
|
||||
if (Dungeon.level.adjacent(pos, enemy.pos)){
|
||||
canReach = true;
|
||||
} else {
|
||||
canReach = (Dungeon.findStep(this, pos, enemy.pos, Dungeon.level.openSpace, fieldOfView) != -1);
|
||||
canReach = (Dungeon.findStep(this, enemy.pos, Dungeon.level.openSpace, fieldOfView, true) != -1);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -121,9 +121,7 @@ public class Piranha extends Mob {
|
|||
return false;
|
||||
}
|
||||
|
||||
int step = Dungeon.findStep( this, pos, target,
|
||||
Dungeon.level.water,
|
||||
fieldOfView );
|
||||
int step = Dungeon.findStep( this, target, Dungeon.level.water, fieldOfView, true );
|
||||
if (step != -1) {
|
||||
move( step );
|
||||
return true;
|
||||
|
@ -134,9 +132,7 @@ public class Piranha extends Mob {
|
|||
|
||||
@Override
|
||||
protected boolean getFurther( int target ) {
|
||||
int step = Dungeon.flee( this, pos, target,
|
||||
Dungeon.level.water,
|
||||
fieldOfView );
|
||||
int step = Dungeon.flee( this, target, Dungeon.level.water, fieldOfView, true );
|
||||
if (step != -1) {
|
||||
move( step );
|
||||
return true;
|
||||
|
|
Loading…
Reference in New Issue
Block a user