diff --git a/core/src/main/assets/messages/actors/actors.properties b/core/src/main/assets/messages/actors/actors.properties
index 4f2c5fb1a..c90e748cf 100644
--- a/core/src/main/assets/messages/actors/actors.properties
+++ b/core/src/main/assets/messages/actors/actors.properties
@@ -1198,6 +1198,12 @@ actors.mobs.dwarfking$dkmonk.rankings_desc=Fell Before the King of Dwarves
actors.mobs.dwarfking$dkwarlock.rankings_desc=Fell Before the King of Dwarves
actors.mobs.dwarfking$dkgolem.rankings_desc=Fell Before the King of Dwarves
+actors.mobs.ebonymimic.name=ebony mimic
+actors.mobs.ebonymimic.reveal=There was a mimic there!
+actors.mobs.ebonymimic.hidden_name=suspicious outline
+actors.mobs.ebonymimic.hidden_desc=There seems to be something here, but it's almost totally transparent.
+actors.mobs.ebonymimic.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\nEbony mimics are extremely tough mimics with the ability to make themselves almost invisible. They lurk on things that adventurers are likely to interact with, but do contain loot of their own as well.
+
actors.mobs.elemental$fireelemental.name=fire elemental
actors.mobs.elemental$fireelemental.desc=Elementals are chaotic creatures that are often created when powerful occult magic isn't properly controlled. Elementals have minimal intelligence, and are usually associated with a particular type of magic.\n\nFire elementals are a common type of elemental which deals damage with fiery magic. They will set their target ablaze with melee attacks, and can occasionally shoot bolts of fire as well.
actors.mobs.elemental$newbornfireelemental.name=newborn fire elemental
diff --git a/core/src/main/assets/messages/items/items.properties b/core/src/main/assets/messages/items/items.properties
index dbbd6d240..de3e2fe20 100644
--- a/core/src/main/assets/messages/items/items.properties
+++ b/core/src/main/assets/messages/items/items.properties
@@ -1304,7 +1304,7 @@ items.trinkets.exoticcrystals.name=exotic crystals
items.trinkets.exoticcrystals.desc=These small pink crystals have the same shape as crystals of alchemical energy. While they can't be used for energy directly, they seem to be somehow influencing the potions and scrolls you find.\n\nAt its current level this trinket will replace _%d%%_ of potion or scroll drops with their exotic equivalents. This does not affect potions of strength, scrolls of upgrade, or items that are generated to help solve hazard rooms.
items.trinkets.mimictooth.name=mimic tooth
-items.trinkets.mimictooth.desc=TODO\n\nAt its current level this trinket will make all kinds of mimics _%sx_ more common, and will make mimics more difficult to detect.
+items.trinkets.mimictooth.desc=This large sharp tooth must have been pulled from a very unhappy mimic. It seems to be influencing the mimics of the dungeon, making them more frequent and dangerous.\n\nAt its current level this trinket will make all kinds of mimic _%1$sx_ more common, will make mimics much more difficult to detect, and will give each floor a _%2$s%%_ chance to contain an ebony mimic.
items.trinkets.mossyclump.name=mossy clump
items.trinkets.mossyclump.desc=This clump of wet moss seems to hold onto its moisture no matter how hard you squeeze it. It seems to be magically tied to the dungeon itself, making grass and water more likely to appear.\n\nAt its current level this trinket will make _%d%%_ of regular floors become filled with either water or grass instead.\n\nThis trinket costs a relatively large amount of energy to upgrade.
diff --git a/core/src/main/assets/sprites/items.png b/core/src/main/assets/sprites/items.png
index 5c78a277f..3f1a41f26 100644
Binary files a/core/src/main/assets/sprites/items.png and b/core/src/main/assets/sprites/items.png differ
diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/EbonyMimic.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/EbonyMimic.java
new file mode 100644
index 000000000..7f3fc7fa0
--- /dev/null
+++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/EbonyMimic.java
@@ -0,0 +1,118 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2015 Oleg Dolya
+ *
+ * Shattered Pixel Dungeon
+ * Copyright (C) 2014-2024 Evan Debenham
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+
+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.effects.CellEmitter;
+import com.shatteredpixel.shatteredpixeldungeon.effects.Speck;
+import com.shatteredpixel.shatteredpixeldungeon.items.EquipableItem;
+import com.shatteredpixel.shatteredpixeldungeon.items.Item;
+import com.shatteredpixel.shatteredpixeldungeon.items.armor.Armor;
+import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.Artifact;
+import com.shatteredpixel.shatteredpixeldungeon.items.wands.Wand;
+import com.shatteredpixel.shatteredpixeldungeon.items.weapon.Weapon;
+import com.shatteredpixel.shatteredpixeldungeon.items.weapon.missiles.MissileWeapon;
+import com.shatteredpixel.shatteredpixeldungeon.levels.Terrain;
+import com.shatteredpixel.shatteredpixeldungeon.levels.features.Door;
+import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
+import com.shatteredpixel.shatteredpixeldungeon.sprites.MimicSprite;
+import com.shatteredpixel.shatteredpixeldungeon.utils.GLog;
+import com.watabou.noosa.audio.Sample;
+import com.watabou.utils.Random;
+
+public class EbonyMimic extends Mimic {
+
+ {
+ spriteClass = MimicSprite.Ebony.class;
+ }
+
+ @Override
+ public String name() {
+ if (alignment == Alignment.NEUTRAL){
+ return Messages.get(this, "hidden_name");
+ } else {
+ return super.name();
+ }
+ }
+
+ @Override
+ public String description() {
+ if (alignment == Alignment.NEUTRAL){
+ return Messages.get(this, "hidden_desc");
+ } else {
+ return super.description();
+ }
+ }
+
+ public void stopHiding(){
+ state = HUNTING;
+ if (sprite != null) sprite.idle();
+ if (Actor.chars().contains(this) && Dungeon.level.heroFOV[pos]) {
+ enemy = Dungeon.hero;
+ target = Dungeon.hero.pos;
+ GLog.w(Messages.get(this, "reveal") );
+ CellEmitter.get(pos).burst(Speck.factory(Speck.STAR), 10);
+ Sample.INSTANCE.play(Assets.Sounds.MIMIC, 1, 0.85f);
+ }
+ if (Dungeon.level.map[pos] == Terrain.DOOR){
+ Door.enter( pos );
+ }
+ }
+
+ @Override
+ public int damageRoll() {
+ if (alignment == Alignment.NEUTRAL){
+ return Math.round(super.damageRoll()*1.5f); //BIG damage on surprise
+ } else {
+ return super.damageRoll();
+ }
+ }
+
+ @Override
+ public void setLevel(int level) {
+ super.setLevel(Math.round(level*1.5f));
+ }
+
+ @Override
+ protected void generatePrize( boolean useDecks ) {
+ super.generatePrize( useDecks );
+ //all existing prize items are guaranteed uncursed, and have a 50% chance to be +1 if they were +0
+ 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);
+ }
+ if (!(i instanceof MissileWeapon || i instanceof Artifact) && i.level() == 0 && Random.Int(2) == 0){
+ i.upgrade();
+ }
+ }
+ }
+ }
+
+}
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 e0ba09ade..83fab97cc 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
@@ -296,6 +296,8 @@ public class Mimic extends Mob {
m = new GoldenMimic();
} else if (mimicType == CrystalMimic.class) {
m = new CrystalMimic();
+ } else if (mimicType == EbonyMimic.class) {
+ m = new EbonyMimic();
} else {
m = new Mimic();
}
diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/trinkets/MimicTooth.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/trinkets/MimicTooth.java
index ede02c843..517a84d25 100644
--- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/trinkets/MimicTooth.java
+++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/trinkets/MimicTooth.java
@@ -38,7 +38,9 @@ public class MimicTooth extends Trinket {
@Override
public String desc() {
- return Messages.get(this, "desc", Float.toString(mimicChanceMultiplier(buffedLvl())));
+ return Messages.get(this, "desc",
+ Messages.decimalFormat("#.##", mimicChanceMultiplier(buffedLvl())),
+ Messages.decimalFormat("#.##", 100*ebonyMimicChance(buffedLvl())));
}
public static float mimicChanceMultiplier(){
@@ -57,4 +59,16 @@ public class MimicTooth extends Trinket {
return trinketLevel(MimicTooth.class) >= 0;
}
+ public static float ebonyMimicChance(){
+ return ebonyMimicChance(trinketLevel(MimicTooth.class));
+ }
+
+ public static float ebonyMimicChance( int level ){
+ if (level >= 0){
+ return 0.125f + 0.125f * level;
+ } else {
+ return 0;
+ }
+ }
+
}
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 821a10c6d..305eab8ab 100644
--- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/RegularLevel.java
+++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/RegularLevel.java
@@ -31,6 +31,7 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.blobs.Blob;
import com.shatteredpixel.shatteredpixeldungeon.actors.blobs.SacrificialFire;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Talent;
+import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.EbonyMimic;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.GoldenMimic;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mimic;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mob;
@@ -630,6 +631,35 @@ public abstract class RegularLevel extends Level {
}
Random.popGenerator();
+ //ebony mimics >:)
+ Random.pushGenerator(Random.Long());
+ if (Random.Float() < MimicTooth.ebonyMimicChance()){
+ ArrayList candidateCells = new ArrayList<>();
+ if (Random.Int(2) == 0){
+ for (Heap h : heaps.valueList()){
+ if (h.type == Heap.Type.HEAP
+ && !(room(h.pos) instanceof SpecialRoom)
+ && findMob(h.pos) == null){
+ candidateCells.add(h.pos);
+ }
+ }
+ } else {
+ if (Random.Int(5) == 0 && findMob(exit()) == null){
+ candidateCells.add(exit());
+ } else {
+ for (int i = 0; i < length(); i++) {
+ if (map[i] == Terrain.DOOR && findMob(i) == null) {
+ candidateCells.add(i);
+ }
+ }
+ }
+ }
+
+ int pos = Random.element(candidateCells);
+ mobs.add(Mimic.spawnAt(pos, EbonyMimic.class, false));
+ }
+ Random.popGenerator();
+
}
private static HashMap limitedDocs = new HashMap<>();
diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/ItemSpriteSheet.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/ItemSpriteSheet.java
index e5a50b7a6..ad10a4e24 100644
--- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/ItemSpriteSheet.java
+++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/ItemSpriteSheet.java
@@ -490,7 +490,7 @@ public class ItemSpriteSheet {
assignItemRect(SUNDIAL, 16, 12);
assignItemRect(CLOVER, 11, 15);
assignItemRect(TRAP_MECHANISM, 13, 15);
- assignItemRect(MIMIC_TOOTH, 7, 13);
+ assignItemRect(MIMIC_TOOTH, 8, 15);
}
private static final int SCROLLS = xy(1, 19); //16 slots
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 341b9a6b0..18bceb41f 100644
--- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/MimicSprite.java
+++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/MimicSprite.java
@@ -28,9 +28,9 @@ import com.watabou.noosa.TextureFilm;
public class MimicSprite extends MobSprite {
- private Animation advancedHiding;
+ protected Animation advancedHiding;
- private Animation hiding;
+ protected Animation hiding;
{
//adjust shadow slightly to account for 1 empty bottom pixel (used for border while hiding)
@@ -68,7 +68,7 @@ public class MimicSprite extends MobSprite {
attack.frames( frames, 3+c, 7+c, 8+c, 9+c );
die = new Animation( 5, false );
- die.frames( frames, 10+c, 111+c, 12+c );
+ die.frames( frames, 10+c, 11+c, 12+c );
play( idle );
}
@@ -112,4 +112,25 @@ public class MimicSprite extends MobSprite {
}
}
+ public static class Ebony extends MimicSprite{
+ @Override
+ protected int texOffset() {
+ return 48;
+ }
+
+ @Override
+ public void hideMimic() {
+ super.hideMimic();
+ alpha(0.2f);
+ }
+
+ @Override
+ public void play(Animation anim) {
+ if (curAnim == advancedHiding && anim != advancedHiding){
+ alpha(1f);
+ }
+ super.play(anim);
+ }
+ }
+
}