diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/Mob.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/Mob.java index b9159ee21..24894ff32 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/Mob.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/Mob.java @@ -239,9 +239,17 @@ public abstract class Mob extends Char { //and add the hero to the list of targets. enemies.add(Dungeon.hero); - - //target one at random. - return Random.element(enemies); + + //go after the closest enemy, preferring the hero if two are equidistant + 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 if (path == null || path.isEmpty() || !Dungeon.level.adjacent(pos, path.getFirst()) - || path.size() >= 2*Dungeon.level.distance(pos, target)) + || path.size() > 2*Dungeon.level.distance(pos, target)) newPath = true; else if (path.getLast() != target) { //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); } - 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; + } step = path.removeFirst(); } @@ -461,12 +475,11 @@ public abstract class Mob extends Char { } } - //become aggro'd by a corrupted enemy - if (enemy.buff(Corruption.class) != null) { + //if attacked by something else than current target, and that thing is closer, switch targets + if (this.enemy == null + || (enemy != this.enemy && (Dungeon.level.distance(pos, enemy.pos) < Dungeon.level.distance(pos, this.enemy.pos)))) { aggro(enemy); target = enemy.pos; - if (state == SLEEPING || state == WANDERING) - state = HUNTING; } if (buff(SoulMark.class) != null) { diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/npcs/MirrorImage.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/npcs/MirrorImage.java index 68cfe00d8..a067d85fd 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/npcs/MirrorImage.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/npcs/MirrorImage.java @@ -32,7 +32,6 @@ import com.shatteredpixel.shatteredpixeldungeon.levels.Level; import com.shatteredpixel.shatteredpixeldungeon.sprites.CharSprite; import com.shatteredpixel.shatteredpixeldungeon.sprites.MirrorSprite; import com.watabou.utils.Bundle; -import com.watabou.utils.Random; import java.util.HashSet; @@ -99,13 +98,23 @@ public class MirrorImage extends NPC { if (enemy == null || !enemy.isAlive()) { HashSet enemies = new HashSet<>(); - for (Mob mob:Dungeon.level.mobs) { - if (mob.hostile && Level.fieldOfView[mob.pos]) { - enemies.add( mob ); + for (Mob mob : Dungeon.level.mobs) { + if (mob.hostile + && 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; diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/artifacts/DriedRose.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/artifacts/DriedRose.java index 7fe3e1acf..784d7ca75 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/artifacts/DriedRose.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/artifacts/DriedRose.java @@ -363,10 +363,16 @@ public class DriedRose extends Artifact { flying = true; + ally = true; + + WANDERING = new Wandering(); + HUNTING = new Hunting(); + state = WANDERING; enemy = null; - - ally = true; + + //after hero, but before mobs + actPriority = 1; } private DriedRose rose = null; @@ -441,25 +447,35 @@ public class DriedRose extends Artifact { } return super.act(); } - - @Override - 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 ); - } - + + //prioritizes closest enemy, and will never attack something far from the player @Override protected Char chooseEnemy() { - if (enemy == null || !enemy.isAlive() || !Dungeon.level.mobs.contains(enemy) || state == WANDERING) { - - HashSet enemies = new HashSet(); + if (enemy == null + || !enemy.isAlive() + || !Dungeon.level.mobs.contains(enemy) + || Dungeon.level.distance(enemy.pos, Dungeon.hero.pos) > 8 + || state == WANDERING) { + + HashSet enemies = new HashSet<>(); 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); } } - 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; } @@ -639,6 +655,75 @@ public class DriedRose extends Artifact { public HashSet> 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.