From 4e7aca628ec107fbe9538f8e74b3f882c9563cec Mon Sep 17 00:00:00 2001 From: Evan Debenham Date: Wed, 11 Dec 2019 14:50:02 -0500 Subject: [PATCH] v0.8.0: finished up mimic implementation, reduced their accuracy a bit --- .../actors/mobs/CrystalMimic.java | 115 +++++++++++++++++- .../actors/mobs/GoldenMimic.java | 26 +++- .../actors/mobs/Mimic.java | 14 ++- .../levels/RegularLevel.java | 4 +- .../levels/rooms/special/VaultRoom.java | 5 + .../sprites/MimicSprite.java | 2 +- .../messages/actors/actors.properties | 10 +- 7 files changed, 162 insertions(+), 14 deletions(-) diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/CrystalMimic.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/CrystalMimic.java index 2db1c7e15..8f048ab9b 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/CrystalMimic.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/CrystalMimic.java @@ -24,22 +24,37 @@ package com.shatteredpixel.shatteredpixeldungeon.actors.mobs; import com.shatteredpixel.shatteredpixeldungeon.Assets; import com.shatteredpixel.shatteredpixeldungeon.Dungeon; 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.Corruption; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Haste; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Terror; +import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero; import com.shatteredpixel.shatteredpixeldungeon.effects.CellEmitter; import com.shatteredpixel.shatteredpixeldungeon.effects.Speck; import com.shatteredpixel.shatteredpixeldungeon.items.Heap; +import com.shatteredpixel.shatteredpixeldungeon.items.Honeypot; import com.shatteredpixel.shatteredpixeldungeon.items.Item; import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.Artifact; import com.shatteredpixel.shatteredpixeldungeon.items.rings.Ring; +import com.shatteredpixel.shatteredpixeldungeon.items.scrolls.ScrollOfTeleportation; import com.shatteredpixel.shatteredpixeldungeon.items.wands.Wand; import com.shatteredpixel.shatteredpixeldungeon.messages.Messages; +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.PathFinder; +import com.watabou.utils.Random; + +import java.util.ArrayList; public class CrystalMimic extends Mimic { { spriteClass = MimicSprite.Crystal.class; + + FLEEING = new Fleeing(); } @Override @@ -54,7 +69,6 @@ public class CrystalMimic extends Mimic { @Override public String description() { if (alignment == Alignment.NEUTRAL){ - //TODO variable based on contents for (Item i : items){ if (i instanceof Artifact){ return Messages.get(Heap.class, "crystal_chest_desc", Messages.get(Heap.class, "artifact")); @@ -70,8 +84,27 @@ public class CrystalMimic extends Mimic { } } + //does not deal bonus damage, steals instead. See attackProc + @Override + public int damageRoll() { + if (alignment == Alignment.NEUTRAL) { + alignment = Alignment.ENEMY; + int dmg = super.damageRoll(); + alignment = Alignment.NEUTRAL; + return dmg; + } else { + return super.damageRoll(); + } + } + public void stopHiding(){ - state = HUNTING; + state = FLEEING; + //haste for 2 turns if attacking + if (alignment == Alignment.NEUTRAL){ + Buff.affect(this, Haste.class, 2f); + } else { + Buff.affect(this, Haste.class, 1f); + } if (Dungeon.level.heroFOV[pos] && Actor.chars().contains(this)) { enemy = Dungeon.hero; target = Dungeon.hero.pos; @@ -82,6 +115,82 @@ public class CrystalMimic extends Mimic { } } - //TODO different AI + @Override + public int attackProc(Char enemy, int damage) { + if (alignment == Alignment.NEUTRAL && enemy == Dungeon.hero){ + steal( Dungeon.hero ); + + } else { + ArrayList candidates = new ArrayList<>(); + for (int i : PathFinder.NEIGHBOURS8){ + if (Dungeon.level.passable[pos+i] && Actor.findChar(pos+i) == null){ + candidates.add(pos + i); + } + } + + if (!candidates.isEmpty()){ + ScrollOfTeleportation.appear(enemy, Random.element(candidates)); + } + + if (alignment == Alignment.ENEMY) state = FLEEING; + } + return super.attackProc(enemy, damage); + } + + protected void steal( Hero hero ) { + + int tries = 10; + Item item; + do { + item = hero.belongings.randomUnequipped(); + } while (tries-- > 0 && (item == null || item.unique || item.level() > 0)); + + if (item != null && !item.unique && item.level() < 1 ) { + + GLog.w( Messages.get(this, "ate", item.name()) ); + if (!item.stackable) { + Dungeon.quickslot.convertToPlaceholder(item); + } + item.updateQuickslot(); + + if (item instanceof Honeypot){ + items.add(((Honeypot)item).shatter(this, this.pos)); + item.detach( hero.belongings.backpack ); + } else { + items.add(item.detach( hero.belongings.backpack )); + if ( item instanceof Honeypot.ShatteredPot) + ((Honeypot.ShatteredPot)item).pickupPot(this); + } + + } + } + + @Override + protected void generatePrize() { + //Crystal mimic already contains a prize item. Just guarantee it isn't cursed. + for (Item i : items){ + i.cursed = false; + i.cursedKnown = true; + } + } + + private class Fleeing extends Mob.Fleeing{ + @Override + protected void nowhereToRun() { + if (buff( Terror.class ) == null && buff( Corruption.class ) == null) { + if (enemySeen) { + sprite.showStatus(CharSprite.NEGATIVE, Messages.get(Mob.class, "rage")); + state = HUNTING; + } else { + GLog.n( Messages.get(CrystalMimic.class, "escaped")); + if (Dungeon.level.heroFOV[pos]) CellEmitter.get(pos).burst(Speck.factory(Speck.WOOL), 6); + destroy(); + sprite.killAndErase(); + } + } else { + super.nowhereToRun(); + } + } + } } diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/GoldenMimic.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/GoldenMimic.java index ff716251e..5a8cade6d 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/GoldenMimic.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/GoldenMimic.java @@ -26,13 +26,17 @@ import com.shatteredpixel.shatteredpixeldungeon.Dungeon; import com.shatteredpixel.shatteredpixeldungeon.actors.Actor; import com.shatteredpixel.shatteredpixeldungeon.effects.CellEmitter; import com.shatteredpixel.shatteredpixeldungeon.effects.Speck; +import com.shatteredpixel.shatteredpixeldungeon.items.EquipableItem; import com.shatteredpixel.shatteredpixeldungeon.items.Heap; +import com.shatteredpixel.shatteredpixeldungeon.items.Item; +import com.shatteredpixel.shatteredpixeldungeon.items.armor.Armor; +import com.shatteredpixel.shatteredpixeldungeon.items.wands.Wand; +import com.shatteredpixel.shatteredpixeldungeon.items.weapon.Weapon; import com.shatteredpixel.shatteredpixeldungeon.messages.Messages; import com.shatteredpixel.shatteredpixeldungeon.sprites.MimicSprite; import com.shatteredpixel.shatteredpixeldungeon.utils.GLog; import com.watabou.noosa.audio.Sample; -//TODO should enhance the reward from these a bit, as they are much harder public class GoldenMimic extends Mimic { { @@ -71,6 +75,24 @@ public class GoldenMimic extends Mimic { @Override public void adjustStats(int level) { - super.adjustStats((int)Math.ceil(level*1.5f)); + super.adjustStats(Math.round(level*1.33f)); + } + + @Override + protected void generatePrize() { + super.generatePrize(); + //all existing prize items are guaranteed uncursed + for (Item i : items){ + if (i instanceof EquipableItem || i instanceof Wand){ + i.cursed = false; + i.cursedKnown = true; + if (i instanceof Weapon && ((Weapon) i).hasCurseEnchant()){ + ((Weapon) i).enchant(null); + } + if (i instanceof Armor && ((Armor) i).hasCurseGlyph()){ + ((Armor) i).inscribe(null); + } + } + } } } 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 c5aaa7d3d..46027f9ba 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 @@ -161,7 +161,7 @@ public class Mimic extends Mob { @Override public int damageRoll() { if (alignment == Alignment.NEUTRAL){ - return Random.NormalIntRange( 2*level, 2 + 3*level); + return Random.NormalIntRange( 2 + 2*level, 3 + 3*level); } else { return Random.NormalIntRange( 1 + level, 2 + 2*level); } @@ -182,7 +182,7 @@ public class Mimic extends Mob { if (target != null && alignment == Alignment.NEUTRAL){ return INFINITE_ACCURACY; } else { - return 9 + level; + return 6 + level; } } @@ -246,6 +246,12 @@ public class Mimic extends Mob { m.pos = pos; //generate an extra reward for killing the mimic + m.generatePrize(); + + return m; + } + + protected void generatePrize(){ Item reward = null; do { switch (Random.Int(5)) { @@ -266,9 +272,7 @@ public class Mimic extends Mob { break; } } while (reward == null || Challenges.isItemBlocked(reward)); - m.items.add(reward); - - return m; + items.add(reward); } { 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 d8259a55e..6271b178d 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/RegularLevel.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/RegularLevel.java @@ -300,7 +300,7 @@ public abstract class RegularLevel extends Level { case 4: type = Heap.Type.CHEST; break; - case 5: + case 5: default: if (Dungeon.depth > 1 && findMob(cell) == null){ mobs.add(Mimic.spawnAt(cell, toDrop)); continue; @@ -312,7 +312,7 @@ public abstract class RegularLevel extends Level { if ((toDrop instanceof Artifact && Random.Int(2) == 0) || (toDrop.isUpgradable() && Random.Int(4 - toDrop.level()) == 0)){ - if (Random.Int(5) == 0 && findMob(cell) == null){ + if (Dungeon.depth > 1 && Random.Int(10) == 0 && findMob(cell) == null){ mobs.add(Mimic.spawnAt(cell, toDrop, GoldenMimic.class)); } else { Heap dropped = drop(toDrop, cell); diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/rooms/special/VaultRoom.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/rooms/special/VaultRoom.java index 71d872eb2..187dbea67 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/rooms/special/VaultRoom.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/rooms/special/VaultRoom.java @@ -41,6 +41,11 @@ import java.util.Arrays; public class VaultRoom extends SpecialRoom { + //size is reduced slightly to remove rare AI issues with crystal mimics + @Override + public int maxHeight() { return 8; } + public int maxWidth() { return 8; } + public void paint( Level level ) { Painter.fill( level, this, Terrain.WALL ); 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 c21ee41a2..09faf9440 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/MimicSprite.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/MimicSprite.java @@ -43,7 +43,7 @@ public class MimicSprite extends MobSprite { TextureFilm frames = new TextureFilm( texture, 16, 16 ); hiding = new Animation( 1, true ); - hiding.frames( frames, 0+c, 0+c, 0+c, 0+c, 0+c, 0+c, 1+c); + hiding.frames( frames, 0+c, 0+c, 0+c, 0+c, 0+c, 1+c); idle = new Animation( 5, true ); idle.frames( frames, 2+c, 2+c, 2+c, 3+c, 3+c ); 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 2d5e75c4a..3723d2fd3 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 @@ -473,6 +473,11 @@ actors.mobs.causticslime.desc=This slime seems to have been tainted by the dark actors.mobs.crab.name=sewer crab actors.mobs.crab.desc=These huge crabs are at the top of the food chain in the sewers. They are extremely fast and their thick carapace can withstand heavy blows. +actors.mobs.crystalmimic.name=crystal mimic +actors.mobs.crystalmimic.ate=The mimic ate your %s! +actors.mobs.crystalmimic.escaped=The crystal mimic has escaped! +actors.mobs.crystalmimic.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.\n\nCrystal mimics are trickier than their regular cousins, and prefer to avoid conflict while stealing loot. They will attempt to sprint away once discovered, and have the ability to reposition enemies when they attack. + actors.mobs.demonspawner.name=demon spawner actors.mobs.demonspawner.desc=This twisting amalgam of dwarven flesh is responsible for creating ripper demons from dwarven body parts. Clearly the demons see no issue with using every resource they can against their enemies.\n\nWhile visually terrifying, demon spawners have no means of moving or directly defending themselves. Their considerable mass makes them hard to kill quickly however, and they will spawn ripper demons more quickly when they are under threat. @@ -515,6 +520,9 @@ actors.mobs.gnoll.desc=Gnolls are hyena-like humanoids. They dwell in sewers and actors.mobs.gnolltrickster.name=gnoll trickster actors.mobs.gnolltrickster.desc=A strange looking creature, even by gnoll standards. It hunches forward with a wicked grin, almost cradling the satchel hanging over its shoulder. Its eyes are wide with a strange mix of fear and excitement.\n\nThere is a large collection of poorly made darts in its satchel, they all seem to be tipped with various harmful substances. +actors.mobs.goldenmimic.name=golden mimic +actors.mobs.goldenmimic.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.\n\nGolden mimics are tougher mimics which try to attract the strongest adventurers. They have the best loot, but are also much stronger than regular mimics. + actors.mobs.golem.name=golem actors.mobs.golem.def_verb=blocked actors.mobs.golem.desc=The Dwarves tried to combine their knowledge of mechanisms with their newfound power of elemental binding. They used spirits of earth as the "soul" for the mechanical bodies of golems, which were believed to be most controllable of all. Despite this, the tiniest mistake in the ritual could cause an outbreak. @@ -554,7 +562,7 @@ actors.mobs.king$undead.desc=These undead dwarves, risen by the will of the King actors.mobs.mimic.name=mimic 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.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.\n\nMimics 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.