v0.6.2: overhauled enemy AI behaviour with respect to alignment
This commit is contained in:
parent
ddaa763a86
commit
925d737d93
|
@ -77,6 +77,14 @@ public abstract class Char extends Actor {
|
|||
public boolean flying = false;
|
||||
public int invisible = 0;
|
||||
|
||||
//these are relative to the hero
|
||||
public enum Alignment{
|
||||
ENEMY,
|
||||
NEUTRAL,
|
||||
ALLY
|
||||
}
|
||||
public Alignment alignment;
|
||||
|
||||
public int viewDistance = 8;
|
||||
|
||||
protected boolean[] fieldOfView = null;
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
package com.shatteredpixel.shatteredpixeldungeon.actors.buffs;
|
||||
|
||||
import com.shatteredpixel.shatteredpixeldungeon.actors.Char;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.sprites.CharSprite;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.ui.BuffIndicator;
|
||||
|
@ -33,6 +34,16 @@ public class Corruption extends Buff {
|
|||
|
||||
private float buildToDamage = 0f;
|
||||
|
||||
@Override
|
||||
public boolean attachTo(Char target) {
|
||||
if (super.attachTo(target)){
|
||||
target.alignment = Char.Alignment.ALLY;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean act() {
|
||||
buildToDamage += target.HT/200f;
|
||||
|
|
|
@ -131,6 +131,8 @@ public class Hero extends Char {
|
|||
|
||||
{
|
||||
actPriority = 0; //acts at priority 0, baseline for the rest of behaviour.
|
||||
|
||||
alignment = Alignment.ALLY;
|
||||
}
|
||||
|
||||
public static final int MAX_LEVEL = 30;
|
||||
|
@ -988,7 +990,7 @@ public class Hero extends Char {
|
|||
|
||||
Mob target = null;
|
||||
for (Mob m : Dungeon.level.mobs) {
|
||||
if (fieldOfView[ m.pos ] && m.hostile) {
|
||||
if (fieldOfView[ m.pos ] && m.alignment == Alignment.ENEMY) {
|
||||
visible.add(m);
|
||||
if (!visibleEnemies.contains( m )) {
|
||||
newMob = true;
|
||||
|
|
|
@ -127,9 +127,13 @@ public class Bee extends Mob {
|
|||
|
||||
//find all mobs near the pot
|
||||
HashSet<Char> enemies = new HashSet<>();
|
||||
for (Mob mob : Dungeon.level.mobs)
|
||||
if (!(mob instanceof Bee) && Dungeon.level.distance(mob.pos, potPos) <= 3 && (mob.hostile || mob.ally))
|
||||
for (Mob mob : Dungeon.level.mobs) {
|
||||
if (!(mob instanceof Bee)
|
||||
&& Dungeon.level.distance(mob.pos, potPos) <= 3
|
||||
&& mob.alignment != Alignment.NEUTRAL) {
|
||||
enemies.add(mob);
|
||||
}
|
||||
}
|
||||
|
||||
//pick one, if there are none, check if the hero is near the pot, go for them, otherwise go for nothing.
|
||||
if (enemies.size() > 0) return Random.element(enemies);
|
||||
|
|
|
@ -62,6 +62,8 @@ public abstract class Mob extends Char {
|
|||
{
|
||||
name = Messages.get(this, "name");
|
||||
actPriority = 2; //hero gets priority over mobs.
|
||||
|
||||
alignment = Alignment.ENEMY;
|
||||
}
|
||||
|
||||
private static final String TXT_DIED = "You hear something died in the distance";
|
||||
|
@ -92,9 +94,6 @@ public abstract class Mob extends Char {
|
|||
|
||||
protected static final float TIME_TO_WAKE_UP = 1f;
|
||||
|
||||
public boolean hostile = true;
|
||||
public boolean ally = false;
|
||||
|
||||
private static final String STATE = "state";
|
||||
private static final String SEEN = "seen";
|
||||
private static final String TARGET = "target";
|
||||
|
@ -194,8 +193,8 @@ public abstract class Mob extends Char {
|
|||
//we have no enemy, or the current one is dead
|
||||
if ( enemy == null || !enemy.isAlive() || state == WANDERING)
|
||||
newEnemy = true;
|
||||
//We are corrupted, and current enemy is either the hero or another corrupted character.
|
||||
else if (buff(Corruption.class) != null && (enemy == Dungeon.hero || enemy.buff(Corruption.class) != null))
|
||||
//We are an ally, and current enemy is another ally.
|
||||
else if (alignment == Alignment.ALLY && enemy.alignment == Alignment.ALLY)
|
||||
newEnemy = true;
|
||||
//We are amoked and current enemy is the hero
|
||||
else if (buff( Amok.class ) != null && enemy == Dungeon.hero)
|
||||
|
@ -205,47 +204,55 @@ public abstract class Mob extends Char {
|
|||
|
||||
HashSet<Char> enemies = new HashSet<>();
|
||||
|
||||
//if the mob is corrupted...
|
||||
if ( buff(Corruption.class) != null) {
|
||||
|
||||
//look for enemy mobs to attack, which are also not corrupted
|
||||
for (Mob mob : Dungeon.level.mobs)
|
||||
if (mob != this && fieldOfView[mob.pos] && mob.hostile && mob.buff(Corruption.class) == null)
|
||||
enemies.add(mob);
|
||||
if (enemies.size() > 0) return Random.element(enemies);
|
||||
|
||||
//otherwise go for nothing
|
||||
return null;
|
||||
|
||||
//if the mob is amoked...
|
||||
} else if ( buff(Amok.class) != null) {
|
||||
|
||||
if ( buff(Amok.class) != null) {
|
||||
//try to find an enemy mob to attack first.
|
||||
for (Mob mob : Dungeon.level.mobs)
|
||||
if (mob != this && fieldOfView[mob.pos] && mob.hostile)
|
||||
if (mob.alignment == Alignment.ENEMY && mob != this && fieldOfView[mob.pos])
|
||||
enemies.add(mob);
|
||||
if (enemies.size() > 0) return Random.element(enemies);
|
||||
|
||||
if (enemies.isEmpty()) {
|
||||
//try to find ally mobs to attack second.
|
||||
for (Mob mob : Dungeon.level.mobs)
|
||||
if (mob != this && fieldOfView[mob.pos] && mob.ally)
|
||||
enemies.add(mob);
|
||||
if (enemies.size() > 0) return Random.element(enemies);
|
||||
|
||||
//if there is nothing, go for the hero
|
||||
else return Dungeon.hero;
|
||||
|
||||
} else {
|
||||
|
||||
//try to find ally mobs to attack.
|
||||
for (Mob mob : Dungeon.level.mobs)
|
||||
if (mob != this && fieldOfView[mob.pos] && mob.ally)
|
||||
if (mob.alignment == Alignment.ALLY && mob != this && fieldOfView[mob.pos])
|
||||
enemies.add(mob);
|
||||
|
||||
//and add the hero to the list of targets.
|
||||
if (enemies.isEmpty()) {
|
||||
//try to find the hero third
|
||||
if (fieldOfView[Dungeon.hero.pos]) {
|
||||
enemies.add(Dungeon.hero);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//go after the closest enemy, preferring the hero if two are equidistant
|
||||
//if the mob is an ally...
|
||||
} else if ( alignment == Alignment.ALLY ) {
|
||||
//look for hostile mobs that are not passive to attack
|
||||
for (Mob mob : Dungeon.level.mobs)
|
||||
if (mob.alignment == Alignment.ENEMY
|
||||
&& fieldOfView[mob.pos]
|
||||
&& mob.state != mob.PASSIVE)
|
||||
enemies.add(mob);
|
||||
|
||||
//if the mob is an enemy...
|
||||
} else if (alignment == Alignment.ENEMY) {
|
||||
//look for ally mobs to attack
|
||||
for (Mob mob : Dungeon.level.mobs)
|
||||
if (mob.alignment == Alignment.ALLY && fieldOfView[mob.pos])
|
||||
enemies.add(mob);
|
||||
|
||||
//and look for the hero
|
||||
if (fieldOfView[Dungeon.hero.pos]) {
|
||||
enemies.add(Dungeon.hero);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//neutral character in particular do not choose enemies.
|
||||
if (enemies.isEmpty()){
|
||||
return null;
|
||||
} else {
|
||||
//go after the closest potential enemy, preferring the hero if two are equidistant
|
||||
Char closest = null;
|
||||
for (Char curr : enemies){
|
||||
if (closest == null
|
||||
|
@ -255,7 +262,6 @@ public abstract class Mob extends Char {
|
|||
}
|
||||
}
|
||||
return closest;
|
||||
|
||||
}
|
||||
|
||||
} else
|
||||
|
@ -538,7 +544,7 @@ public abstract class Mob extends Char {
|
|||
|
||||
if (Dungeon.hero.isAlive()) {
|
||||
|
||||
if (hostile) {
|
||||
if (alignment == Alignment.ENEMY) {
|
||||
Statistics.enemiesSlain++;
|
||||
Badges.validateMonstersSlain();
|
||||
Statistics.qualifiedForNoKilling = false;
|
||||
|
@ -566,7 +572,7 @@ public abstract class Mob extends Char {
|
|||
Dungeon.level.drop( loot , pos ).sprite.drop();
|
||||
}
|
||||
|
||||
if (hostile && Dungeon.hero.lvl <= maxLvl + 2){
|
||||
if (alignment == Alignment.ENEMY && Dungeon.hero.lvl <= maxLvl + 2){
|
||||
int rolls = 1;
|
||||
if (properties.contains(Property.BOSS)) rolls = 15;
|
||||
else if (properties.contains(Property.MINIBOSS)) rolls = 5;
|
||||
|
@ -723,6 +729,10 @@ public abstract class Mob extends Char {
|
|||
|
||||
if (enemyInFOV) {
|
||||
target = enemy.pos;
|
||||
} else if (enemy == null) {
|
||||
state = WANDERING;
|
||||
target = Dungeon.level.randomDestination();
|
||||
return true;
|
||||
}
|
||||
|
||||
int oldPos = pos;
|
||||
|
@ -732,9 +742,7 @@ public abstract class Mob extends Char {
|
|||
return moveSprite( oldPos, pos );
|
||||
|
||||
} else {
|
||||
|
||||
spend( TICK );
|
||||
|
||||
if (!enemyInFOV) {
|
||||
sprite.showLost();
|
||||
state = WANDERING;
|
||||
|
|
|
@ -27,18 +27,16 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.blobs.ToxicGas;
|
|||
import com.shatteredpixel.shatteredpixeldungeon.actors.blobs.VenomGas;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Burning;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mob;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.sprites.CharSprite;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.sprites.MirrorSprite;
|
||||
import com.watabou.utils.Bundle;
|
||||
|
||||
import java.util.HashSet;
|
||||
|
||||
public class MirrorImage extends NPC {
|
||||
|
||||
{
|
||||
spriteClass = MirrorSprite.class;
|
||||
|
||||
alignment = Alignment.ALLY;
|
||||
state = HUNTING;
|
||||
}
|
||||
|
||||
|
@ -93,32 +91,6 @@ public class MirrorImage extends NPC {
|
|||
return damage;
|
||||
}
|
||||
|
||||
protected Char chooseEnemy() {
|
||||
|
||||
if (enemy == null || !enemy.isAlive()) {
|
||||
HashSet<Mob> enemies = new HashSet<>();
|
||||
for (Mob mob : Dungeon.level.mobs) {
|
||||
if (mob.hostile
|
||||
&& fieldOfView[mob.pos]
|
||||
&& mob.state != mob.PASSIVE) {
|
||||
enemies.add(mob);
|
||||
}
|
||||
}
|
||||
|
||||
//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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSprite sprite() {
|
||||
CharSprite s = super.sprite();
|
||||
|
|
|
@ -33,7 +33,7 @@ public abstract class NPC extends Mob {
|
|||
HP = HT = 1;
|
||||
EXP = 0;
|
||||
|
||||
hostile = false;
|
||||
alignment = Alignment.NEUTRAL;
|
||||
state = PASSIVE;
|
||||
}
|
||||
|
||||
|
|
|
@ -378,13 +378,11 @@ public class DriedRose extends Artifact {
|
|||
|
||||
flying = true;
|
||||
|
||||
ally = true;
|
||||
alignment = Alignment.ALLY;
|
||||
|
||||
WANDERING = new Wandering();
|
||||
HUNTING = new Hunting();
|
||||
|
||||
state = WANDERING;
|
||||
enemy = null;
|
||||
state = HUNTING;
|
||||
|
||||
//after hero, but before mobs
|
||||
actPriority = 1;
|
||||
|
@ -470,11 +468,12 @@ public class DriedRose extends Artifact {
|
|||
|| !enemy.isAlive()
|
||||
|| !Dungeon.level.mobs.contains(enemy)
|
||||
|| Dungeon.level.distance(enemy.pos, Dungeon.hero.pos) > 8
|
||||
|| enemy.alignment != Alignment.ENEMY
|
||||
|| state == WANDERING) {
|
||||
|
||||
HashSet<Mob> enemies = new HashSet<>();
|
||||
for (Mob mob : Dungeon.level.mobs) {
|
||||
if (mob.hostile
|
||||
if (mob.alignment == Alignment.ENEMY
|
||||
&& fieldOfView[mob.pos]
|
||||
&& Dungeon.level.distance(mob.pos, Dungeon.hero.pos) <= 8
|
||||
&& mob.state != mob.PASSIVE) {
|
||||
|
@ -676,12 +675,14 @@ public class DriedRose extends Artifact {
|
|||
enemySeen = true;
|
||||
|
||||
notice();
|
||||
alerted = true;
|
||||
state = HUNTING;
|
||||
target = enemy.pos;
|
||||
|
||||
} else {
|
||||
|
||||
enemySeen = false;
|
||||
sprite.hideLost();
|
||||
|
||||
int oldPos = pos;
|
||||
//always move towards the hero when wandering
|
||||
|
@ -702,40 +703,6 @@ public class DriedRose extends Artifact {
|
|||
|
||||
}
|
||||
|
||||
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.
|
||||
//************************************************************************************
|
||||
|
|
|
@ -29,7 +29,6 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff;
|
|||
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Invisibility;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.LockedFloor;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mob;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.effects.MagicMissile;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.items.Item;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.items.scrolls.ScrollOfTeleportation;
|
||||
|
@ -118,7 +117,7 @@ public class LloydsBeacon extends Artifact {
|
|||
|
||||
for (int i = 0; i < PathFinder.NEIGHBOURS8.length; i++) {
|
||||
Char ch = Actor.findChar(hero.pos + PathFinder.NEIGHBOURS8[i]);
|
||||
if (ch != null && !(ch instanceof Mob && !((Mob) ch).hostile)) {
|
||||
if (ch != null && ch.alignment == Char.Alignment.ENEMY) {
|
||||
GLog.w( Messages.get(this, "creatures") );
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -23,12 +23,10 @@ package com.shatteredpixel.shatteredpixeldungeon.items.wands;
|
|||
|
||||
import com.shatteredpixel.shatteredpixeldungeon.Assets;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.Dungeon;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.tiles.DungeonTilemap;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.actors.Actor;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.actors.Char;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Charm;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Corruption;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mob;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.effects.Beam;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.effects.CellEmitter;
|
||||
|
@ -48,6 +46,7 @@ import com.shatteredpixel.shatteredpixeldungeon.plants.Plant;
|
|||
import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.sprites.CharSprite;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.sprites.ItemSpriteSheet;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.tiles.DungeonTilemap;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.utils.GLog;
|
||||
import com.watabou.noosa.audio.Sample;
|
||||
import com.watabou.utils.Bundle;
|
||||
|
@ -83,8 +82,8 @@ public class WandOfTransfusion extends Wand {
|
|||
|
||||
processSoulMark(ch, chargesPerCast());
|
||||
|
||||
//heals an ally, or charmed/corrupted enemy
|
||||
if (((Mob) ch).ally || ch.buff(Charm.class) != null || ch.buff(Corruption.class) != null){
|
||||
//heals an ally, or a charmed enemy
|
||||
if (ch.alignment == Char.Alignment.ALLY || ch.buff(Charm.class) != null){
|
||||
|
||||
int missingHP = ch.HT - ch.HP;
|
||||
//heals 30%+3%*lvl missing HP.
|
||||
|
|
|
@ -227,7 +227,7 @@ public class CavesBossLevel extends Level {
|
|||
|
||||
for (Mob m : mobs){
|
||||
//bring the first ally with you
|
||||
if (m.ally){
|
||||
if (m.alignment == Char.Alignment.ALLY){
|
||||
m.pos = Dungeon.hero.pos + (Random.Int(2) == 0 ? +1 : -1);
|
||||
m.sprite.place(m.pos);
|
||||
break;
|
||||
|
|
|
@ -198,7 +198,7 @@ public class CityBossLevel extends Level {
|
|||
|
||||
for (Mob m : mobs){
|
||||
//bring the first ally with you
|
||||
if (m.ally){
|
||||
if (m.alignment == Char.Alignment.ALLY){
|
||||
m.pos = Dungeon.hero.pos + (Random.Int(2) == 0 ? +1 : -1);
|
||||
m.sprite.place(m.pos);
|
||||
break;
|
||||
|
|
|
@ -503,7 +503,7 @@ public abstract class Level implements Bundlable {
|
|||
protected boolean act() {
|
||||
int count = 0;
|
||||
for (Mob mob : mobs.toArray(new Mob[0])){
|
||||
if (mob.hostile) count++;
|
||||
if (mob.alignment == Char.Alignment.ENEMY) count++;
|
||||
}
|
||||
|
||||
if (count < nMobs()) {
|
||||
|
|
|
@ -287,7 +287,7 @@ public class PrisonBossLevel extends Level {
|
|||
|
||||
for (Mob m : mobs){
|
||||
//bring the first ally with you
|
||||
if (m.ally){
|
||||
if (m.alignment == Char.Alignment.ALLY){
|
||||
m.pos = 5 + 25 * 32; //they should immediately walk out of the door
|
||||
m.sprite.place(m.pos);
|
||||
break;
|
||||
|
@ -340,7 +340,7 @@ public class PrisonBossLevel extends Level {
|
|||
|
||||
//if any allies are left over, move them along the same way as the hero
|
||||
for (Mob m : mobs){
|
||||
if (m.ally) {
|
||||
if (m.alignment == Char.Alignment.ALLY) {
|
||||
m.pos += 9 + 3 * 32;
|
||||
m.sprite().place(m.pos);
|
||||
}
|
||||
|
@ -386,7 +386,7 @@ public class PrisonBossLevel extends Level {
|
|||
//remove all mobs, but preserve allies
|
||||
ArrayList<Mob> allies = new ArrayList<>();
|
||||
for(Mob m : mobs.toArray(new Mob[0])){
|
||||
if (m.ally){
|
||||
if (m.alignment == Char.Alignment.ALLY){
|
||||
allies.add(m);
|
||||
mobs.remove(m);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user