v0.6.1: several improvements to mob and ally AI
This commit is contained in:
parent
7ebf612917
commit
cbc21ef9fb
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in New Issue
Block a user