diff --git a/core/src/main/assets/mimic.png b/core/src/main/assets/mimic.png index f8f43aa85..1d8e5f201 100644 Binary files a/core/src/main/assets/mimic.png and b/core/src/main/assets/mimic.png differ diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/Mimic.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/Mimic.java index d7e226c23..01e522254 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/Mimic.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/Mimic.java @@ -31,17 +31,22 @@ import com.shatteredpixel.shatteredpixeldungeon.effects.Pushing; import com.shatteredpixel.shatteredpixeldungeon.effects.Speck; import com.shatteredpixel.shatteredpixeldungeon.items.Generator; import com.shatteredpixel.shatteredpixeldungeon.items.Gold; +import com.shatteredpixel.shatteredpixeldungeon.items.Heap; import com.shatteredpixel.shatteredpixeldungeon.items.Item; import com.shatteredpixel.shatteredpixeldungeon.items.scrolls.ScrollOfRetribution; import com.shatteredpixel.shatteredpixeldungeon.items.scrolls.exotic.ScrollOfPsionicBlast; +import com.shatteredpixel.shatteredpixeldungeon.messages.Messages; import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene; +import com.shatteredpixel.shatteredpixeldungeon.sprites.CharSprite; import com.shatteredpixel.shatteredpixeldungeon.sprites.MimicSprite; +import com.shatteredpixel.shatteredpixeldungeon.utils.GLog; import com.watabou.noosa.audio.Sample; import com.watabou.utils.Bundle; import com.watabou.utils.PathFinder; import com.watabou.utils.Random; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.List; @@ -53,6 +58,12 @@ public class Mimic extends Mob { spriteClass = MimicSprite.class; properties.add(Property.DEMONIC); + + EXP = 0; + + //mimics are neutral when hidden + alignment = Alignment.NEUTRAL; + state = PASSIVE; } public ArrayList items; @@ -75,24 +86,111 @@ public class Mimic extends Mob { } adjustStats( bundle.getInt( LEVEL ) ); super.restoreFromBundle(bundle); + if (state != PASSIVE && alignment == Alignment.NEUTRAL){ + alignment = Alignment.ENEMY; + } } - + + @Override + public String name() { + if (alignment == Alignment.NEUTRAL){ + return Messages.get(Heap.class, "chest"); + } else { + return super.name(); + } + } + + @Override + public String description() { + if (alignment == Alignment.NEUTRAL){ + return Messages.get(Heap.class, "chest_desc"); + } else { + return super.description(); + } + } + + @Override + public CharSprite sprite() { + MimicSprite sprite = (MimicSprite) super.sprite(); + if (alignment == Alignment.NEUTRAL) sprite.hideMimic(); + return sprite; + } + + @Override + public boolean interact() { + if (alignment != Alignment.NEUTRAL){ + return super.interact(); + } + stopHiding(); + doAttack(Dungeon.hero); + Dungeon.hero.busy(); + Dungeon.hero.sprite.operate(pos); + return false; + } + + @Override + public void onAttackComplete() { + super.onAttackComplete(); + if (alignment == Alignment.NEUTRAL){ + alignment = Alignment.ENEMY; + Dungeon.hero.spendAndNext(1f); + } + } + + @Override + public void damage(int dmg, Object src) { + if (state == PASSIVE){ + alignment = Alignment.ENEMY; + stopHiding(); + } + super.damage(dmg, src); + } + + public void stopHiding(){ + state = HUNTING; + if (Dungeon.level.heroFOV[pos] && Actor.chars().contains(this)) { + enemy = Dungeon.hero; + target = Dungeon.hero.pos; + enemySeen = true; + GLog.w(Messages.get(this, "reveal") ); + CellEmitter.get(pos).burst(Speck.factory(Speck.STAR), 10); + Sample.INSTANCE.play(Assets.SND_MIMIC); + } + } + @Override public int damageRoll() { - return Random.NormalIntRange( HT / 10, HT / 4 ); + if (alignment == Alignment.NEUTRAL){ + return Random.NormalIntRange( 2*level, 2 + 3*level); + } else { + return Random.NormalIntRange( 1 + level, 2 + 2*level); + } } - + + @Override + public int drRoll() { + return Random.NormalIntRange(0, 1 + level/2); + } + + @Override + public void beckon( int cell ) { + // Do nothing + } + @Override public int attackSkill( Char target ) { - return 9 + level; + if (target != null && alignment == Alignment.NEUTRAL){ + return INFINITE_ACCURACY; + } else { + return 9 + level; + } } public void adjustStats( int level ) { this.level = level; HP = HT = (1 + level) * 6; - EXP = 2 + 2 * (level - 1) / 5; - defenseSkill = attackSkill( null ) / 2; + defenseSkill = 2 + level/2; enemySeen = true; } @@ -120,42 +218,16 @@ public class Mimic extends Mob { return true; } + public static Mimic spawnAt( int pos, Item item ){ + return spawnAt( pos, Arrays.asList(item)); + } + public static Mimic spawnAt( int pos, List items ) { - if (Dungeon.level.pit[pos]) return null; - Char ch = Actor.findChar( pos ); - if (ch != null) { - ArrayList candidates = new ArrayList<>(); - for (int n : PathFinder.NEIGHBOURS8) { - int cell = pos + n; - if ((Dungeon.level.passable[cell] || Dungeon.level.avoid[cell]) && Actor.findChar( cell ) == null) { - candidates.add( cell ); - } - } - if (candidates.size() > 0) { - int newPos = Random.element( candidates ); - Actor.addDelayed( new Pushing( ch, ch.pos, newPos ), -1 ); - - ch.pos = newPos; - Dungeon.level.occupyCell(ch ); - - } else { - return null; - } - } Mimic m = new Mimic(); m.items = new ArrayList<>( items ); m.adjustStats( Dungeon.depth ); m.pos = pos; - m.state = m.HUNTING; - GameScene.add( m, 1 ); - - m.sprite.turnTo( pos, Dungeon.hero.pos ); - - if (Dungeon.level.heroFOV[m.pos]) { - CellEmitter.get( pos ).burst( Speck.factory( Speck.STAR ), 10 ); - Sample.INSTANCE.play( Assets.SND_MIMIC ); - } //generate an extra reward for killing the mimic Item reward = null; diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/Heap.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/Heap.java index e9ce9a843..9dcf06441 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/Heap.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/Heap.java @@ -72,7 +72,7 @@ public class Heap implements Bundlable { TOMB, SKELETON, REMAINS, - MIMIC + MIMIC //remains for pre-0.8.0 compatibility. There are converted to mimics on level load } public Type type = Type.HEAP; @@ -87,11 +87,7 @@ public class Heap implements Bundlable { public void open( Hero hero ) { switch (type) { case MIMIC: - if (Mimic.spawnAt(pos, items) != null) { - destroy(); - } else { - type = Type.CHEST; - } + type = Type.CHEST; break; case TOMB: Wraith.spawnAround( hero.pos ); @@ -207,15 +203,6 @@ public class Heap implements Bundlable { public void burn() { - if (type == Type.MIMIC) { - Mimic m = Mimic.spawnAt( pos, items ); - if (m != null) { - Buff.affect( m, Burning.class ).reignite( m ); - m.sprite.emitter().burst( FlameParticle.FACTORY, 5 ); - destroy(); - } - } - if (type != Type.HEAP) { return; } @@ -285,7 +272,7 @@ public class Heap implements Bundlable { for (Item item : items.toArray( new Item[0] )) { if (item instanceof Potion) { - items.remove( item ); + items.remove(item); ((Potion) item).shatter(pos); } else if (item instanceof Bomb) { @@ -313,14 +300,6 @@ public class Heap implements Bundlable { public void freeze() { - if (type == Type.MIMIC) { - Mimic m = Mimic.spawnAt( pos, items ); - if (m != null) { - Buff.prolong( m, Frost.class, Frost.duration( m ) * Random.Float( 1.0f, 1.5f ) ); - destroy(); - } - } - if (type != Type.HEAP) { return; } diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/bombs/Noisemaker.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/bombs/Noisemaker.java index 09f35a7a4..e4038f498 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/bombs/Noisemaker.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/bombs/Noisemaker.java @@ -52,16 +52,6 @@ public class Noisemaker extends Bomb { mob.beckon( cell ); } - for (Heap heap : Dungeon.level.heaps.valueList()) { - if (heap.type == Heap.Type.MIMIC) { - Mimic m = Mimic.spawnAt( heap.pos, heap.items ); - if (m != null) { - m.beckon( cell ); - heap.destroy(); - } - } - } - } public static class Trigger extends Buff { diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/scrolls/ScrollOfRage.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/scrolls/ScrollOfRage.java index 9f972bd79..9a8794146 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/scrolls/ScrollOfRage.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/scrolls/ScrollOfRage.java @@ -51,16 +51,6 @@ public class ScrollOfRage extends Scroll { } } - for (Heap heap : Dungeon.level.heaps.valueList()) { - if (heap.type == Heap.Type.MIMIC) { - Mimic m = Mimic.spawnAt( heap.pos, heap.items ); - if (m != null) { - m.beckon( curUser.pos ); - heap.destroy(); - } - } - } - GLog.w( Messages.get(this, "roar") ); setKnown(); diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/wands/CursedWand.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/wands/CursedWand.java index 23bcd8ec3..f55f67869 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/wands/CursedWand.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/wands/CursedWand.java @@ -370,8 +370,11 @@ public class CursedWand { case 1: cursedFX(user, bolt, new Callback() { public void call() { + //TODO make this a gold mimic instead of boosting stats artificially Mimic mimic = Mimic.spawnAt(bolt.collisionPos, new ArrayList()); if (mimic != null) { + mimic.stopHiding(); + mimic.alignment = Char.Alignment.ENEMY; mimic.adjustStats(Dungeon.depth + 10); mimic.HP = mimic.HT; Item reward; @@ -382,6 +385,7 @@ public class CursedWand { Sample.INSTANCE.play(Assets.SND_MIMIC, 1, 1, 0.5f); mimic.items.clear(); mimic.items.add(reward); + GameScene.add(mimic); } else { GLog.i(Messages.get(CursedWand.class, "nothing")); } diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/Level.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/Level.java index 509d93d2b..89316b91b 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/Level.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/Level.java @@ -44,6 +44,7 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.HeroClass; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.HeroSubClass; import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Bestiary; +import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mimic; import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mob; import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.Sheep; import com.shatteredpixel.shatteredpixeldungeon.effects.particles.FlowParticle; @@ -375,6 +376,14 @@ public abstract class Level implements Bundlable { buildFlagMaps(); cleanWalls(); + + //compat with pre-0.8.0 saves + for (Heap h : heaps.valueList()){ + if (h.type == Heap.Type.MIMIC){ + heaps.remove(h.pos); + mobs.add(Mimic.spawnAt(h.pos, h.items)); + } + } } @Override diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/RegularLevel.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/RegularLevel.java index 57cb675d6..0ea060f97 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/RegularLevel.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/RegularLevel.java @@ -24,6 +24,7 @@ package com.shatteredpixel.shatteredpixeldungeon.levels; import com.shatteredpixel.shatteredpixeldungeon.Bones; import com.shatteredpixel.shatteredpixeldungeon.Dungeon; import com.shatteredpixel.shatteredpixeldungeon.actors.Actor; +import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mimic; import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mob; import com.shatteredpixel.shatteredpixeldungeon.items.Generator; import com.shatteredpixel.shatteredpixeldungeon.items.Heap; @@ -277,6 +278,16 @@ public abstract class RegularLevel extends Level { int nItems = 3 + Random.chances(new float[]{6, 3, 1}); for (int i=0; i < nItems; i++) { + + Item toDrop = Generator.random(); + if (toDrop == null) continue; + + int cell = randomDropCell(); + if (map[cell] == Terrain.HIGH_GRASS || map[cell] == Terrain.FURROWED_GRASS) { + map[cell] = Terrain.GRASS; + losBlocking[cell] = false; + } + Heap.Type type = null; switch (Random.Int( 20 )) { case 0: @@ -289,21 +300,17 @@ public abstract class RegularLevel extends Level { type = Heap.Type.CHEST; break; case 5: - type = Dungeon.depth > 1 ? Heap.Type.MIMIC : Heap.Type.CHEST; + if (Dungeon.depth > 1 && findMob(cell) == null){ + mobs.add(Mimic.spawnAt(cell, toDrop)); + continue; + } + type = Heap.Type.CHEST; break; default: type = Heap.Type.HEAP; } - int cell = randomDropCell(); - if (map[cell] == Terrain.HIGH_GRASS || map[cell] == Terrain.FURROWED_GRASS) { - map[cell] = Terrain.GRASS; - losBlocking[cell] = false; - } - - Item toDrop = Generator.random(); - - if (toDrop == null) continue; + //TODO gold mimics if ((toDrop instanceof Artifact && Random.Int(2) == 0) || (toDrop.isUpgradable() && Random.Int(4 - toDrop.level()) == 0)){ Heap dropped = drop( toDrop, cell ); diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/rooms/special/TreasuryRoom.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/rooms/special/TreasuryRoom.java index 5a223ce63..f435840aa 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/rooms/special/TreasuryRoom.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/rooms/special/TreasuryRoom.java @@ -22,6 +22,7 @@ package com.shatteredpixel.shatteredpixeldungeon.levels.rooms.special; import com.shatteredpixel.shatteredpixeldungeon.Dungeon; +import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mimic; import com.shatteredpixel.shatteredpixeldungeon.items.Gold; import com.shatteredpixel.shatteredpixeldungeon.items.Heap; import com.shatteredpixel.shatteredpixeldungeon.items.keys.IronKey; @@ -47,7 +48,11 @@ public class TreasuryRoom extends SpecialRoom { do { pos = level.pointToCell(random()); } while (level.map[pos] != Terrain.EMPTY || level.heaps.get( pos ) != null); - level.drop( new Gold().random(), pos ).type = (Random.Int(20) == 0 && heapType == Heap.Type.CHEST ? Heap.Type.MIMIC : heapType); + if (heapType == Heap.Type.CHEST && Random.Int(5 ) == 0){ + level.mobs.add(Mimic.spawnAt(pos, new Gold().random())); + } else { + level.drop( new Gold().random(), pos ).type = heapType; + } } if (heapType == Heap.Type.HEAP) { diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/rooms/standard/SuspiciousChestRoom.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/rooms/standard/SuspiciousChestRoom.java index 9ae33b91a..8ea7367e9 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/rooms/standard/SuspiciousChestRoom.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/rooms/standard/SuspiciousChestRoom.java @@ -21,6 +21,7 @@ package com.shatteredpixel.shatteredpixeldungeon.levels.rooms.standard; +import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mimic; import com.shatteredpixel.shatteredpixeldungeon.items.Gold; import com.shatteredpixel.shatteredpixeldungeon.items.Heap; import com.shatteredpixel.shatteredpixeldungeon.items.Item; @@ -56,7 +57,7 @@ public class SuspiciousChestRoom extends EmptyRoom { Painter.set(level, center, Terrain.PEDESTAL); if (Random.Int(3) == 0) { - level.drop(i, center).type = Heap.Type.MIMIC; + level.mobs.add(Mimic.spawnAt(center, i)); } else { level.drop(i, center).type = Heap.Type.CHEST; } diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/traps/DistortionTrap.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/traps/DistortionTrap.java index c63ce67d4..4094488a7 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/traps/DistortionTrap.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/traps/DistortionTrap.java @@ -23,6 +23,7 @@ package com.shatteredpixel.shatteredpixeldungeon.levels.traps; import com.shatteredpixel.shatteredpixeldungeon.Dungeon; import com.shatteredpixel.shatteredpixeldungeon.actors.Actor; +import com.shatteredpixel.shatteredpixeldungeon.actors.Char; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero; import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Acidic; import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Albino; @@ -121,8 +122,10 @@ public class DistortionTrap extends Trap{ mob = new Piranha(); break; case 2: - Mimic.spawnAt(point, new ArrayList<>()); - continue; //mimics spawn themselves, no need to do more + mob = Mimic.spawnAt(point, new ArrayList<>()); + ((Mimic)mob).stopHiding(); + mob.alignment = Char.Alignment.ENEMY; + break; case 3: mob = Statue.random(); break; diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/MimicSprite.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/MimicSprite.java index 866aa529e..507fd0912 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/MimicSprite.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/MimicSprite.java @@ -22,34 +22,57 @@ package com.shatteredpixel.shatteredpixeldungeon.sprites; import com.shatteredpixel.shatteredpixeldungeon.Assets; +import com.shatteredpixel.shatteredpixeldungeon.actors.Char; import com.watabou.noosa.TextureFilm; public class MimicSprite extends MobSprite { - + + private Animation hiding; + public MimicSprite() { super(); - + texture( Assets.MIMIC ); - + TextureFilm frames = new TextureFilm( texture, 16, 16 ); - + + hiding = new Animation( 1, true ); + hiding.frames( frames, 0, 0, 0, 0, 0, 0, 1); + idle = new Animation( 5, true ); - idle.frames( frames, 0, 0, 0, 1, 1 ); - + idle.frames( frames, 2, 2, 2, 3, 3 ); + run = new Animation( 10, true ); - run.frames( frames, 0, 1, 2, 3, 3, 2, 1 ); - + run.frames( frames, 2, 3, 4, 5, 5, 4, 3 ); + attack = new Animation( 10, false ); - attack.frames( frames, 0, 4, 5, 6 ); - + attack.frames( frames, 2, 6, 7, 8 ); + die = new Animation( 5, false ); - die.frames( frames, 7, 8, 9 ); - + die.frames( frames, 9, 10, 11 ); + play( idle ); } @Override - public int blood() { - return 0xFFcb9700; + public void linkVisuals(Char ch) { + super.linkVisuals(ch); + if (ch.alignment == Char.Alignment.NEUTRAL) { + hideMimic(); + } } + + public void hideMimic(){ + play(hiding); + hideSleep(); + } + + @Override + public synchronized void showSleep() { + if (curAnim == hiding){ + return; + } + super.showSleep(); + } + } diff --git a/core/src/main/resources/com/shatteredpixel/shatteredpixeldungeon/messages/actors/actors.properties b/core/src/main/resources/com/shatteredpixel/shatteredpixeldungeon/messages/actors/actors.properties index cbe9f1e83..2d5e75c4a 100644 --- a/core/src/main/resources/com/shatteredpixel/shatteredpixeldungeon/messages/actors/actors.properties +++ b/core/src/main/resources/com/shatteredpixel/shatteredpixeldungeon/messages/actors/actors.properties @@ -553,7 +553,8 @@ actors.mobs.king$undead.rankings_desc=Fell Before the King of Dwarves actors.mobs.king$undead.desc=These undead dwarves, risen by the will of the King of Dwarves, were members of his court. They appear as skeletons with a stunning amount of facial hair. actors.mobs.mimic.name=mimic -actors.mobs.mimic.desc=Mimics are magical creatures which can take any shape they wish. In dungeons they almost always choose a shape of a treasure chest, because they know how to beckon an adventurer. +actors.mobs.mimic.reveal=That chest is a mimic! +actors.mobs.mimic.desc=Mimics are magical creatures which can take any shape they wish. In dungeons they almost always choose a shape of a treasure chest, in order to lure in unsuspecting adventurers. Mimics have a nasty bite, but often hold more treasure than a regular chest. actors.mobs.necromancer.name=necromancer actors.mobs.necromancer.desc=These apprentice dark mages have flocked to the prison, as it is the perfect place to practise their evil craft.\n\nNecromancers will summon and empower skeletons to fight for them. Killing the necromancer will also kill the skeleton it summons.