diff --git a/core/src/main/assets/messages/actors/actors.properties b/core/src/main/assets/messages/actors/actors.properties
index d618f8bc7..85b35bb1f 100644
--- a/core/src/main/assets/messages/actors/actors.properties
+++ b/core/src/main/assets/messages/actors/actors.properties
@@ -1197,6 +1197,10 @@ actors.mobs.monk.desc=These monks are fanatics, who have devoted themselves to p
actors.mobs.monk$focus.name=focused
actors.mobs.monk$focus.desc=This monk is perfectly honed in on their target, and seem to be anticipating their moves before they make them.\n\nWhile focused, the next physical attack made against this character is guaranteed to miss, no matter what circumstances there are. Parrying this attack will spend the monk's focus, and they will need to build it up again to parry another attack. Monks build focus more quickly while they are moving.
+actors.mobs.phantompiranha.name=phantom piranha
+actors.mobs.phantompiranha.teleport_away=The phantom piranha teleports away...
+actors.mobs.phantompiranha.desc=TODO
+
actors.mobs.piranha.name=giant piranha
actors.mobs.piranha.desc=These carnivorous fish are not natural inhabitants of underground pools. They were bred specifically to protect flooded treasure vaults.
diff --git a/core/src/main/assets/messages/items/items.properties b/core/src/main/assets/messages/items/items.properties
index d4e7ad3f1..57a3925a6 100644
--- a/core/src/main/assets/messages/items/items.properties
+++ b/core/src/main/assets/messages/items/items.properties
@@ -568,6 +568,9 @@ items.food.mysterymeat.stuffed=You are stuffed.
items.food.mysterymeat.desc=Eat at your own risk!
items.food.mysterymeat$placeholder.name=meat
+items.food.phantommeat.name=phantom meat
+items.food.phantommeat.desc=TODO
+
items.food.pasty.pasty=pasty
items.food.pasty.pie=pumpkin pie
items.food.pasty.cane=candy cane
diff --git a/core/src/main/assets/sprites/items.png b/core/src/main/assets/sprites/items.png
index c34b39568..432b0c92b 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/assets/sprites/piranha.png b/core/src/main/assets/sprites/piranha.png
index ee7fe105f..6ab0f16d7 100644
Binary files a/core/src/main/assets/sprites/piranha.png and b/core/src/main/assets/sprites/piranha.png differ
diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/PhantomPiranha.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/PhantomPiranha.java
new file mode 100644
index 000000000..1748802db
--- /dev/null
+++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/PhantomPiranha.java
@@ -0,0 +1,112 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2015 Oleg Dolya
+ *
+ * Shattered Pixel Dungeon
+ * Copyright (C) 2014-2023 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.Dungeon;
+import com.shatteredpixel.shatteredpixeldungeon.actors.Actor;
+import com.shatteredpixel.shatteredpixeldungeon.actors.Char;
+import com.shatteredpixel.shatteredpixeldungeon.items.food.PhantomMeat;
+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.PhantomPiranhaSprite;
+import com.shatteredpixel.shatteredpixeldungeon.utils.GLog;
+import com.watabou.utils.PathFinder;
+import com.watabou.utils.Random;
+
+import java.util.ArrayList;
+
+public class PhantomPiranha extends Piranha {
+
+ {
+ spriteClass = PhantomPiranhaSprite.class;
+
+ loot = PhantomMeat.class;
+ lootChance = 1f;
+ }
+
+ @Override
+ public void damage(int dmg, Object src) {
+ Char dmgSource = null;
+ if (src instanceof Char) dmgSource = (Char)src;
+ if (src instanceof Wand) dmgSource = Dungeon.hero;
+
+ if (dmgSource == null || !Dungeon.level.adjacent(pos, dmgSource.pos)){
+ dmg = Math.round(dmg/2f); //halve damage taken if we are going to teleport
+ }
+ super.damage(dmg, src);
+
+ if (isAlive()) {
+ if (dmgSource != null) {
+ if (!Dungeon.level.adjacent(pos, dmgSource.pos)) {
+ ArrayList candidates = new ArrayList<>();
+ for (int i : PathFinder.NEIGHBOURS8) {
+ if (Dungeon.level.water[dmgSource.pos + i] && Actor.findChar(dmgSource.pos + i) == null) {
+ candidates.add(dmgSource.pos + i);
+ }
+ }
+ if (!candidates.isEmpty()) {
+ ScrollOfTeleportation.appear(this, Random.element(candidates));
+ aggro(dmgSource);
+ } else {
+ teleportAway();
+ }
+ }
+ } else {
+ teleportAway();
+ }
+ }
+ }
+
+ @Override
+ public int defenseProc(Char enemy, int damage) {
+ return super.defenseProc(enemy, damage);
+ }
+
+ @Override
+ public void dieOnLand() {
+ teleportAway();
+ }
+
+ private void teleportAway(){
+
+ ArrayList inFOVCandidates = new ArrayList<>();
+ ArrayList outFOVCandidates = new ArrayList<>();
+ for (int i = 0; i < Dungeon.level.length(); i++){
+ if (Dungeon.level.water[i] && Actor.findChar(i) == null){
+ if (Dungeon.level.heroFOV[i]){
+ inFOVCandidates.add(i);
+ } else {
+ outFOVCandidates.add(i);
+ }
+ }
+ }
+
+ if (!outFOVCandidates.isEmpty()){
+ ScrollOfTeleportation.appear(this, Random.element(outFOVCandidates));
+ GLog.i(Messages.get(this, "teleport_away"));
+ } else if (!inFOVCandidates.isEmpty()){
+ ScrollOfTeleportation.appear(this, Random.element(inFOVCandidates));
+ }
+
+ }
+}
diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/Piranha.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/Piranha.java
index eb1947dd1..8aafbbf0f 100644
--- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/Piranha.java
+++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/Piranha.java
@@ -66,7 +66,7 @@ public class Piranha extends Mob {
protected boolean act() {
if (!Dungeon.level.water[pos]) {
- die( null );
+ dieOnLand();
return true;
} else {
return super.act();
@@ -99,7 +99,11 @@ public class Piranha extends Mob {
}
return super.surprisedBy(enemy, attacking);
}
-
+
+ public void dieOnLand(){
+ die( null );
+ }
+
@Override
public void die( Object cause ) {
super.die( cause );
@@ -191,4 +195,12 @@ public class Piranha extends Mob {
return super.act(enemyInFOV, justAlerted);
}
}
+
+ public static Piranha random(){
+ if (Random.Int(50) == 0){
+ return new PhantomPiranha();
+ } else {
+ return new Piranha();
+ }
+ }
}
diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/artifacts/HornOfPlenty.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/artifacts/HornOfPlenty.java
index fa1dd3d92..5c48458de 100644
--- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/artifacts/HornOfPlenty.java
+++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/artifacts/HornOfPlenty.java
@@ -39,6 +39,7 @@ import com.shatteredpixel.shatteredpixeldungeon.items.food.Blandfruit;
import com.shatteredpixel.shatteredpixeldungeon.items.food.Food;
import com.shatteredpixel.shatteredpixeldungeon.items.food.MeatPie;
import com.shatteredpixel.shatteredpixeldungeon.items.food.Pasty;
+import com.shatteredpixel.shatteredpixeldungeon.items.food.PhantomMeat;
import com.shatteredpixel.shatteredpixeldungeon.items.rings.RingOfEnergy;
import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene;
@@ -217,8 +218,8 @@ public class HornOfPlenty extends Artifact {
if (level() >= 10) return;
storedFoodEnergy += food.energy;
- //Pasties are worth two upgrades instead of 1.5, meat pies are worth 4 instead of 3!
- if (food instanceof Pasty){
+ //Pasties and phantom meat are worth two upgrades instead of 1.5, meat pies are worth 4 instead of 3!
+ if (food instanceof Pasty || food instanceof PhantomMeat){
storedFoodEnergy += Hunger.HUNGRY/2;
} else if (food instanceof MeatPie){
storedFoodEnergy += Hunger.HUNGRY;
diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/food/MeatPie.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/food/MeatPie.java
index 1fff06541..6f2c1e630 100644
--- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/food/MeatPie.java
+++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/food/MeatPie.java
@@ -58,7 +58,7 @@ public class MeatPie extends Food {
for (Item ingredient : ingredients){
if (ingredient.quantity() > 0) {
- if (ingredient instanceof Pasty) {
+ if (ingredient instanceof Pasty || ingredient instanceof PhantomMeat) {
pasty = true;
} else if (ingredient.getClass() == Food.class) {
ration = true;
diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/food/PhantomMeat.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/food/PhantomMeat.java
new file mode 100644
index 000000000..82fb669e0
--- /dev/null
+++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/food/PhantomMeat.java
@@ -0,0 +1,63 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2015 Oleg Dolya
+ *
+ * Shattered Pixel Dungeon
+ * Copyright (C) 2014-2023 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.items.food;
+
+import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Barkskin;
+import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff;
+import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Hunger;
+import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Invisibility;
+import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero;
+import com.shatteredpixel.shatteredpixeldungeon.effects.Speck;
+import com.shatteredpixel.shatteredpixeldungeon.items.potions.PotionOfHealing;
+import com.shatteredpixel.shatteredpixeldungeon.sprites.ItemSpriteSheet;
+
+public class PhantomMeat extends Food {
+
+ {
+ image = ItemSpriteSheet.PHANTOM_MEAT;
+ energy = Hunger.STARVING;
+ }
+
+ @Override
+ protected void satisfy(Hero hero) {
+ super.satisfy(hero);
+ effect(hero);
+ }
+
+ public int value() {
+ return 30 * quantity;
+ }
+
+ public static void effect(Hero hero){
+
+ Buff.affect( hero, Barkskin.class ).set( hero.HT / 4, 1 );
+ Buff.affect( hero, Invisibility.class, Invisibility.DURATION );
+ if (hero.HP < hero.HT) {
+ hero.HP = Math.min( hero.HP + hero.HT / 4, hero.HT );
+ }
+ hero.sprite.emitter().burst( Speck.factory( Speck.HEALING ), 1 );
+ PotionOfHealing.cure(hero);
+
+ }
+
+
+}
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 c874e32f1..36841c21d 100644
--- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/Level.java
+++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/Level.java
@@ -1081,7 +1081,7 @@ public abstract class Level implements Bundlable {
}
if (ch.isAlive() && ch instanceof Piranha && !water[ch.pos]){
- ch.die(null);
+ ((Piranha) ch).dieOnLand();
}
}
diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/rooms/special/PoolRoom.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/rooms/special/PoolRoom.java
index 94b67d66b..f9d171c1c 100644
--- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/rooms/special/PoolRoom.java
+++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/rooms/special/PoolRoom.java
@@ -90,7 +90,7 @@ public class PoolRoom extends SpecialRoom {
level.addItemToSpawn( new PotionOfInvisibility() );
for (int i=0; i < NPIRANHAS; i++) {
- Piranha piranha = new Piranha();
+ Piranha piranha = Piranha.random();
do {
piranha.pos = level.pointToCell(random());
} while (level.map[piranha.pos] != Terrain.WATER|| level.findMob( piranha.pos ) != null);
diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/rooms/standard/AquariumRoom.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/rooms/standard/AquariumRoom.java
index 9c2d5dc81..16b152a15 100644
--- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/rooms/standard/AquariumRoom.java
+++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/rooms/standard/AquariumRoom.java
@@ -65,7 +65,7 @@ public class AquariumRoom extends StandardRoom {
int numFish = (minDim - 4)/3; //1-3 fish, depending on room size
for (int i=0; i < numFish; i++) {
- Piranha piranha = new Piranha();
+ Piranha piranha = Piranha.random();
do {
piranha.pos = level.pointToCell(random(3));
} while (level.map[piranha.pos] != Terrain.WATER|| level.findMob( piranha.pos ) != null);
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 0bd44665e..b599a9cc6 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
@@ -120,7 +120,7 @@ public class DistortionTrap extends Trap{
continue; //wraiths spawn themselves, no need to do more
case 1:
//yes it's intended that these are likely to die right away
- mob = new Piranha();
+ mob = Piranha.random();
break;
case 2:
mob = Mimic.spawnAt(point, new ArrayList<>());
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 e6d2a6687..70b4aae8a 100644
--- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/ItemSpriteSheet.java
+++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/ItemSpriteSheet.java
@@ -645,6 +645,7 @@ public class ItemSpriteSheet {
public static final int BLANDFRUIT = FOOD+10;
public static final int BLAND_CHUNKS= FOOD+11;
public static final int BERRY = FOOD+12;
+ public static final int PHANTOM_MEAT= FOOD+13;
static{
assignItemRect(MEAT, 15, 11);
assignItemRect(STEAK, 15, 11);
@@ -659,6 +660,7 @@ public class ItemSpriteSheet {
assignItemRect(BLANDFRUIT, 9, 12);
assignItemRect(BLAND_CHUNKS,14, 6);
assignItemRect(BERRY, 9, 11);
+ assignItemRect(PHANTOM_MEAT,15, 11);
}
private static final int QUEST = xy(1, 29); //32 slots
diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/PhantomPiranhaSprite.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/PhantomPiranhaSprite.java
new file mode 100644
index 000000000..48fbb45bb
--- /dev/null
+++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/PhantomPiranhaSprite.java
@@ -0,0 +1,110 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2015 Oleg Dolya
+ *
+ * Shattered Pixel Dungeon
+ * Copyright (C) 2014-2023 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.sprites;
+
+import com.shatteredpixel.shatteredpixeldungeon.Assets;
+import com.shatteredpixel.shatteredpixeldungeon.actors.Char;
+import com.shatteredpixel.shatteredpixeldungeon.effects.Speck;
+import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene;
+import com.watabou.noosa.MovieClip;
+import com.watabou.noosa.TextureFilm;
+import com.watabou.noosa.particles.Emitter;
+
+public class PhantomPiranhaSprite extends MobSprite {
+
+ private Emitter sparkles;
+
+ public PhantomPiranhaSprite() {
+ super();
+
+ renderShadow = false;
+ perspectiveRaise = 0.2f;
+
+ texture( Assets.Sprites.PIRANHA );
+
+ TextureFilm frames = new TextureFilm( texture, 12, 16 );
+
+ int c = 21;
+
+ idle = new MovieClip.Animation( 8, true );
+ idle.frames( frames, c+0, c+1, c+2, c+1 );
+
+ run = new MovieClip.Animation( 20, true );
+ run.frames( frames, c+0, c+1, c+2, c+1 );
+
+ attack = new MovieClip.Animation( 20, false );
+ attack.frames( frames, c+3, c+4, c+5, c+6, c+7, c+8, c+9, c+10, c+11 );
+
+ die = new MovieClip.Animation( 4, false );
+ die.frames( frames, c+12, c+13, c+14 );
+
+ play( idle );
+ }
+
+ @Override
+ public void link(Char ch) {
+ super.link(ch);
+ renderShadow = false;
+
+ if (sparkles == null) {
+ sparkles = emitter();
+ sparkles.pour( Speck.factory( Speck.LIGHT ), 0.5f );
+ }
+ }
+
+ @Override
+ public void update() {
+ super.update();
+
+ if (sparkles != null) {
+ sparkles.visible = visible;
+ }
+ }
+
+ @Override
+ public void die() {
+ super.die();
+
+ if (sparkles != null) {
+ sparkles.on = false;
+ }
+ }
+
+ @Override
+ public void kill() {
+ super.kill();
+
+ if (sparkles != null) {
+ sparkles.on = false;
+ }
+ }
+
+ @Override
+ public void onComplete( MovieClip.Animation anim ) {
+ super.onComplete( anim );
+
+ if (anim == attack) {
+ GameScene.ripple( ch.pos );
+ }
+ }
+}
+