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 ); + } + } +} +