v0.6.1: several improvements to mob and ally AI

This commit is contained in:
Evan Debenham 2017-06-29 03:41:53 -04:00
parent 7ebf612917
commit cbc21ef9fb
3 changed files with 136 additions and 29 deletions

View File

@ -239,9 +239,17 @@ public abstract class Mob extends Char {
//and add the hero to the list of targets. //and add the hero to the list of targets.
enemies.add(Dungeon.hero); enemies.add(Dungeon.hero);
//target one at random. //go after the closest enemy, preferring the hero if two are equidistant
return Random.element(enemies); Char closest = null;
for (Char curr : enemies){
if (closest == null
|| Dungeon.level.distance(pos, curr.pos) < Dungeon.level.distance(pos, closest.pos)
|| Dungeon.level.distance(pos, curr.pos) == Dungeon.level.distance(pos, closest.pos) && curr == Dungeon.hero){
closest = curr;
}
}
return closest;
} }
@ -313,7 +321,7 @@ public abstract class Mob extends Char {
//or if it's extremely inefficient and checking again may result in a much better path //or if it's extremely inefficient and checking again may result in a much better path
if (path == null || path.isEmpty() if (path == null || path.isEmpty()
|| !Dungeon.level.adjacent(pos, path.getFirst()) || !Dungeon.level.adjacent(pos, path.getFirst())
|| path.size() >= 2*Dungeon.level.distance(pos, target)) || path.size() > 2*Dungeon.level.distance(pos, target))
newPath = true; newPath = true;
else if (path.getLast() != target) { else if (path.getLast() != target) {
//if the new target is adjacent to the end of the path, adjust for that //if the new target is adjacent to the end of the path, adjust for that
@ -372,8 +380,14 @@ public abstract class Mob extends Char {
Level.fieldOfView); Level.fieldOfView);
} }
if (path == null) //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)))) {
return false; return false;
}
step = path.removeFirst(); step = path.removeFirst();
} }
@ -461,12 +475,11 @@ public abstract class Mob extends Char {
} }
} }
//become aggro'd by a corrupted enemy //if attacked by something else than current target, and that thing is closer, switch targets
if (enemy.buff(Corruption.class) != null) { if (this.enemy == null
|| (enemy != this.enemy && (Dungeon.level.distance(pos, enemy.pos) < Dungeon.level.distance(pos, this.enemy.pos)))) {
aggro(enemy); aggro(enemy);
target = enemy.pos; target = enemy.pos;
if (state == SLEEPING || state == WANDERING)
state = HUNTING;
} }
if (buff(SoulMark.class) != null) { if (buff(SoulMark.class) != null) {

View File

@ -32,7 +32,6 @@ import com.shatteredpixel.shatteredpixeldungeon.levels.Level;
import com.shatteredpixel.shatteredpixeldungeon.sprites.CharSprite; import com.shatteredpixel.shatteredpixeldungeon.sprites.CharSprite;
import com.shatteredpixel.shatteredpixeldungeon.sprites.MirrorSprite; import com.shatteredpixel.shatteredpixeldungeon.sprites.MirrorSprite;
import com.watabou.utils.Bundle; import com.watabou.utils.Bundle;
import com.watabou.utils.Random;
import java.util.HashSet; import java.util.HashSet;
@ -99,13 +98,23 @@ public class MirrorImage extends NPC {
if (enemy == null || !enemy.isAlive()) { if (enemy == null || !enemy.isAlive()) {
HashSet<Mob> enemies = new HashSet<>(); HashSet<Mob> enemies = new HashSet<>();
for (Mob mob:Dungeon.level.mobs) { for (Mob mob : Dungeon.level.mobs) {
if (mob.hostile && Level.fieldOfView[mob.pos]) { if (mob.hostile
enemies.add( mob ); && Level.fieldOfView[mob.pos]
&& mob.state != mob.PASSIVE) {
enemies.add(mob);
} }
} }
enemy = enemies.size() > 0 ? Random.element( enemies ) : null; //go for closest enemy
Char closest = null;
for (Char curr : enemies){
if (closest == null
|| Dungeon.level.distance(pos, curr.pos) < Dungeon.level.distance(pos, closest.pos)){
closest = curr;
}
}
return closest;
} }
return enemy; return enemy;

View File

@ -363,10 +363,16 @@ public class DriedRose extends Artifact {
flying = true; flying = true;
ally = true;
WANDERING = new Wandering();
HUNTING = new Hunting();
state = WANDERING; state = WANDERING;
enemy = null; enemy = null;
ally = true; //after hero, but before mobs
actPriority = 1;
} }
private DriedRose rose = null; private DriedRose rose = null;
@ -441,25 +447,35 @@ public class DriedRose extends Artifact {
} }
return super.act(); return super.act();
} }
@Override //prioritizes closest enemy, and will never attack something far from the player
protected boolean getCloser( int target ) {
if (state == WANDERING || Dungeon.level.distance(target, Dungeon.hero.pos) > 6)
this.target = target = Dungeon.hero.pos;
return super.getCloser( target );
}
@Override @Override
protected Char chooseEnemy() { protected Char chooseEnemy() {
if (enemy == null || !enemy.isAlive() || !Dungeon.level.mobs.contains(enemy) || state == WANDERING) { if (enemy == null
|| !enemy.isAlive()
HashSet<Mob> enemies = new HashSet<Mob>(); || !Dungeon.level.mobs.contains(enemy)
|| Dungeon.level.distance(enemy.pos, Dungeon.hero.pos) > 8
|| state == WANDERING) {
HashSet<Mob> enemies = new HashSet<>();
for (Mob mob : Dungeon.level.mobs) { for (Mob mob : Dungeon.level.mobs) {
if (mob.hostile && Level.fieldOfView[mob.pos] && mob.state != mob.PASSIVE) { if (mob.hostile
&& Level.fieldOfView[mob.pos]
&& Dungeon.level.distance(mob.pos, Dungeon.hero.pos) <= 8
&& mob.state != mob.PASSIVE) {
enemies.add(mob); enemies.add(mob);
} }
} }
enemy = enemies.size() > 0 ? Random.element( enemies ) : null;
//go for closest enemy
Char closest = null;
for (Char curr : enemies){
if (closest == null
|| Dungeon.level.distance(pos, curr.pos) < Dungeon.level.distance(pos, closest.pos)){
closest = curr;
}
}
return closest;
} }
return enemy; return enemy;
} }
@ -639,6 +655,75 @@ public class DriedRose extends Artifact {
public HashSet<Class<?>> immunities() { public HashSet<Class<?>> immunities() {
return IMMUNITIES; return IMMUNITIES;
} }
private class Wandering extends Mob.Wandering {
@Override
public boolean act( boolean enemyInFOV, boolean justAlerted ) {
if ( enemyInFOV ) {
enemySeen = true;
notice();
state = HUNTING;
target = enemy.pos;
} else {
enemySeen = false;
int oldPos = pos;
//always move towards the hero when wandering
if (getCloser( target = Dungeon.hero.pos )) {
//moves 2 tiles at a time when returning to the hero from a distance
if (!Dungeon.level.adjacent(Dungeon.hero.pos, pos)){
getCloser( target = Dungeon.hero.pos );
}
spend( 1 / speed() );
return moveSprite( oldPos, pos );
} else {
spend( TICK );
}
}
return true;
}
}
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;
}
int oldPos = pos;
if (enemyInFOV && getCloser( target )) {
spend( 1 / speed() );
return moveSprite( oldPos, pos );
} else {
//don't lose a turn due to the transition, immediately act instead
state = WANDERING;
return state.act( false, justAlerted );
}
}
}
}
//************************************************************************************ //************************************************************************************
//This is a bunch strings & string arrays, used in all of the sad ghost's voice lines. //This is a bunch strings & string arrays, used in all of the sad ghost's voice lines.