From 67132b929f26c5c6d3a726c5a133d72ae9b06eea Mon Sep 17 00:00:00 2001 From: Evan Debenham Date: Thu, 27 Apr 2023 17:37:38 -0400 Subject: [PATCH] v2.1.0: added a new exotic enemy: tormented spirits --- .../assets/messages/actors/actors.properties | 4 + .../assets/messages/items/items.properties | 1 + core/src/main/assets/sprites/wraith.png | Bin 296 -> 491 bytes .../actors/mobs/Mob.java | 2 +- .../actors/mobs/SpectralNecromancer.java | 2 +- .../actors/mobs/TormentedSpirit.java | 78 ++++++++++++++++++ .../actors/mobs/Wraith.java | 24 ++++-- .../shatteredpixeldungeon/items/Heap.java | 4 +- .../items/artifacts/DriedRose.java | 2 +- .../items/quest/CorpseDust.java | 2 +- .../items/scrolls/ScrollOfRemoveCurse.java | 31 +++++++ .../levels/traps/DistortionTrap.java | 2 +- .../sprites/TormentedSpiritSprite.java | 57 +++++++++++++ 13 files changed, 195 insertions(+), 14 deletions(-) create mode 100644 core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/TormentedSpirit.java create mode 100644 core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/TormentedSpiritSprite.java diff --git a/core/src/main/assets/messages/actors/actors.properties b/core/src/main/assets/messages/actors/actors.properties index 2f27ef23a..b9f4699ea 100644 --- a/core/src/main/assets/messages/actors/actors.properties +++ b/core/src/main/assets/messages/actors/actors.properties @@ -1289,6 +1289,10 @@ actors.mobs.thief.carries=\n\nThe thief is carrying a _%s._ Stolen obviously. actors.mobs.thief.escapes=The thief gets away with your %s! actors.mobs.thief.desc=Though these inmates roam free of their cells, this place is still their prison. Over time, this place has taken their minds as well as their freedom. Long ago, these crazy thieves and bandits have forgotten who they are and why they steal.\n\nThese enemies are more likely to steal and run than they are to fight. Make sure to keep them in sight, or you might never see your stolen item again. +actors.mobs.tormentedspirit.name=tormented spirit +actors.mobs.tormentedspirit.desc=Tormented spirits are otherwise good-natured spirits that has been afflicted by a curse. So long as they are cursed they will attack just like a wraith, and are more powerful as well!\n\nIt may be possible to cleanse the curse by using the right item while next to the spirit. If the curse is lifted the spirit will surely be grateful... +actors.mobs.tormentedspirit.thank_you=Thank you... + actors.mobs.warlock.name=dwarf warlock actors.mobs.warlock.bolt_kill=The shadow bolt killed you... actors.mobs.warlock.desc=As the dwarves' interests shifted from engineering to arcane arts, warlocks came to power in the city. They started with elemental magic, but soon switched to demonology and necromancy. The strongest of these warlocks seized the throne of the dwarven city, and his cohorts were allowed to continue practising their dark magic, so long as they surrendered their free will to him.\n\nThese warlocks possess powerful disruptive magic, and are able to temporarily hinder the upgrade magic applied to your equipment. The more upgraded an item is, the more strongly it will be affected. diff --git a/core/src/main/assets/messages/items/items.properties b/core/src/main/assets/messages/items/items.properties index 175c42989..d4e7ad3f1 100644 --- a/core/src/main/assets/messages/items/items.properties +++ b/core/src/main/assets/messages/items/items.properties @@ -996,6 +996,7 @@ items.scrolls.scrollofremovecurse.name=scroll of remove curse items.scrolls.scrollofremovecurse.inv_title=Cleanse an item items.scrolls.scrollofremovecurse.cleansed=Your item glows with a cleansing light, and a malevolent energy disperses! items.scrolls.scrollofremovecurse.not_cleansed=Your item glows with a cleansing light, but nothing happens. +items.scrolls.scrollofremovecurse.spirit=Your scroll frees the tormented spirit! items.scrolls.scrollofremovecurse.desc=The incantation on this scroll will instantly strip any curses from a single weapon, ring, wand, armor, or artifact. items.scrolls.scrollofteleportation.name=scroll of teleportation diff --git a/core/src/main/assets/sprites/wraith.png b/core/src/main/assets/sprites/wraith.png index 6f9e0d77a155651be3000c758c483d9e64b4cc7c..0799079e15c5e50fd886c42edf908362cc204dca 100644 GIT binary patch delta 478 zcmZ3%^qP5sc)bD(GXn#I22ZXSkdg@S32_C|aPa^Ce`5v)AgX6z2wdCW0hC}b3GxeO zsBifHzrMk`#PBu)17o$Pi(^Oyuwteuv}1NTq#zsr*KL9M`uy9NaocGnKvi= zXKF0{dx2ShqxKGin3P%U#p&W=^U@jXAAG2j3G&+W$?1lz;Gfjnc84pzWZEt~^p5kn z{{Ff;y-yuGauw`Pm@6fQM|2e5nZnFuW#gc-ZHs`o-KA|i7o6xaIFWzCQQ8N?VD&$ljuvE=}y_b;c*K)aQr4{3u;MDSX!LzX4}im0R!rWaOwQKa;_D z$_MDc)|-cd0>KVU40m1ybl>-@H&?ARG6uUZ%Cu5G_EY|kAS3Cxof8=tOqMKuwL$p$ Qdr+)-y85}Sb4q9e0R0oy;s5{u delta 282 zcmaFOyn<+|9>D80)XtmR-Z>e4s%J6Uob;`!~RUA z<3R3FPZ!6K3dT9-7_*K#aIjp6^xyC-KEYJ{$J)J)@9UReN)+{En!@onE->otov?|PE{BgjZN dZl@vxgQDLdq4mH diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/Mob.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/Mob.java index d71f750fd..9cf8e94e7 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/Mob.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/Mob.java @@ -811,7 +811,7 @@ public abstract class Mob extends Char { if (!(this instanceof Wraith) && soulMarked && Random.Float() < (0.4f*Dungeon.hero.pointsInTalent(Talent.NECROMANCERS_MINIONS)/3f)){ - Wraith w = Wraith.spawnAt(pos); + Wraith w = Wraith.spawnAt(pos, false); if (w != null) { Buff.affect(w, Corruption.class); if (Dungeon.level.heroFOV[pos]) { diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/SpectralNecromancer.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/SpectralNecromancer.java index caae8432b..0041b1d60 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/SpectralNecromancer.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/SpectralNecromancer.java @@ -148,7 +148,7 @@ public class SpectralNecromancer extends Necromancer { summoning = firstSummon = false; - Wraith wraith = Wraith.spawnAt(summoningPos); + Wraith wraith = Wraith.spawnAt(summoningPos, false); wraith.adjustStats(0); Dungeon.level.occupyCell( wraith ); ((SpectralNecromancerSprite)sprite).finishSummoning(); diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/TormentedSpirit.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/TormentedSpirit.java new file mode 100644 index 000000000..a62d1614d --- /dev/null +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/TormentedSpirit.java @@ -0,0 +1,78 @@ +/* + * 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.Assets; +import com.shatteredpixel.shatteredpixeldungeon.Dungeon; +import com.shatteredpixel.shatteredpixeldungeon.effects.Speck; +import com.shatteredpixel.shatteredpixeldungeon.effects.particles.ShaftParticle; +import com.shatteredpixel.shatteredpixeldungeon.items.Generator; +import com.shatteredpixel.shatteredpixeldungeon.items.Item; +import com.shatteredpixel.shatteredpixeldungeon.items.armor.Armor; +import com.shatteredpixel.shatteredpixeldungeon.items.weapon.melee.MeleeWeapon; +import com.shatteredpixel.shatteredpixeldungeon.messages.Messages; +import com.shatteredpixel.shatteredpixeldungeon.sprites.TormentedSpiritSprite; +import com.watabou.noosa.audio.Sample; +import com.watabou.utils.Random; + +public class TormentedSpirit extends Wraith { + + { + spriteClass = TormentedSpiritSprite.class; + } + + @Override + public void adjustStats(int level) { + super.adjustStats(Math.round(level*1.5f)); + } + + public void cleanse(){ + Sample.INSTANCE.play( Assets.Sounds.GHOST ); + yell(Messages.get(this, "thank_you")); + + //50/50 between weapon or armor, always uncursed + Item prize; + if (Random.Int(2) == 0){ + prize = Generator.randomWeapon(); + if (((MeleeWeapon)prize).hasCurseEnchant()){ + ((MeleeWeapon) prize).enchantment = null; + } + } else { + prize = Generator.randomArmor(); + if (((Armor) prize).hasCurseGlyph()){ + ((Armor) prize).glyph = null; + } + } + prize.cursed = false; + prize.cursedKnown = true; + + Dungeon.level.drop(prize, pos).sprite.drop(); + + destroy(); + sprite.die(); + sprite.tint(1, 1, 1, 1); + sprite.emitter().start( ShaftParticle.FACTORY, 0.3f, 4 ); + sprite.emitter().start( Speck.factory( Speck.LIGHT ), 0.2f, 3 ); + + } + +} diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/Wraith.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/Wraith.java index 829cc4945..d6c52d0e4 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/Wraith.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/Wraith.java @@ -24,6 +24,7 @@ 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.effects.particles.ChallengeParticle; import com.shatteredpixel.shatteredpixeldungeon.effects.particles.ShadowParticle; import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene; import com.shatteredpixel.shatteredpixeldungeon.sprites.WraithSprite; @@ -93,16 +94,21 @@ public class Wraith extends Mob { return true; } - public static void spawnAround( int pos ) { + public static void spawnAround( int pos, boolean allowExotic ) { for (int n : PathFinder.NEIGHBOURS4) { - spawnAt( pos + n ); + spawnAt( pos + n, allowExotic ); } } - public static Wraith spawnAt( int pos ) { + public static Wraith spawnAt( int pos, boolean allowExotic ) { if ((!Dungeon.level.solid[pos] || Dungeon.level.passable[pos]) && Actor.findChar( pos ) == null) { - - Wraith w = new Wraith(); + + Wraith w; + if (allowExotic && Random.Int(1) == 0){ + w = new TormentedSpirit(); + } else { + w = new Wraith(); + } w.adjustStats( Dungeon.scalingDepth() ); w.pos = pos; w.state = w.HUNTING; @@ -111,8 +117,12 @@ public class Wraith extends Mob { w.sprite.alpha( 0 ); w.sprite.parent.add( new AlphaTweener( w.sprite, 1, 0.5f ) ); - - w.sprite.emitter().burst( ShadowParticle.CURSE, 5 ); + + if (w instanceof TormentedSpirit){ + w.sprite.emitter().burst(ChallengeParticle.FACTORY, 10); + } else { + w.sprite.emitter().burst(ShadowParticle.CURSE, 5); + } return w; } else { 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 a75202304..269ab38d2 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/Heap.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/Heap.java @@ -83,7 +83,7 @@ public class Heap implements Bundlable { public void open( Hero hero ) { switch (type) { case TOMB: - Wraith.spawnAround( hero.pos ); + Wraith.spawnAround( hero.pos, true ); break; case REMAINS: case SKELETON: @@ -93,7 +93,7 @@ public class Heap implements Bundlable { } if (haunted){ - if (Wraith.spawnAt( pos ) == null) { + if (Wraith.spawnAt( pos, true ) == null) { hero.sprite.emitter().burst( ShadowParticle.CURSE, 6 ); hero.damage( hero.HP / 2, this ); } diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/artifacts/DriedRose.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/artifacts/DriedRose.java index bb6757d34..a10f6033f 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/artifacts/DriedRose.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/artifacts/DriedRose.java @@ -440,7 +440,7 @@ public class DriedRose extends Artifact { } if (spawnPoints.size() > 0) { - Wraith.spawnAt(Random.element(spawnPoints)); + Wraith.spawnAt(Random.element(spawnPoints), false); Sample.INSTANCE.play(Assets.Sounds.CURSED); } diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/quest/CorpseDust.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/quest/CorpseDust.java index bcad1061c..56d434f5d 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/quest/CorpseDust.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/quest/CorpseDust.java @@ -119,7 +119,7 @@ public class CorpseDust extends Item { tries --; } while (tries > 0 && (!Dungeon.level.heroFOV[pos] || Dungeon.level.solid[pos] || Actor.findChar( pos ) != null)); if (tries > 0) { - Wraith.spawnAt(pos); + Wraith.spawnAt(pos, false); Sample.INSTANCE.play(Assets.Sounds.CURSED); } } diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/scrolls/ScrollOfRemoveCurse.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/scrolls/ScrollOfRemoveCurse.java index 1409520ac..5e6dec3aa 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/scrolls/ScrollOfRemoveCurse.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/scrolls/ScrollOfRemoveCurse.java @@ -21,10 +21,13 @@ package com.shatteredpixel.shatteredpixeldungeon.items.scrolls; +import com.shatteredpixel.shatteredpixeldungeon.Assets; import com.shatteredpixel.shatteredpixeldungeon.Dungeon; +import com.shatteredpixel.shatteredpixeldungeon.actors.Actor; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Degrade; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Belongings; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero; +import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.TormentedSpirit; import com.shatteredpixel.shatteredpixeldungeon.effects.Flare; import com.shatteredpixel.shatteredpixeldungeon.effects.particles.ShadowParticle; import com.shatteredpixel.shatteredpixeldungeon.items.EquipableItem; @@ -35,6 +38,8 @@ import com.shatteredpixel.shatteredpixeldungeon.items.weapon.Weapon; import com.shatteredpixel.shatteredpixeldungeon.messages.Messages; import com.shatteredpixel.shatteredpixeldungeon.sprites.ItemSpriteSheet; import com.shatteredpixel.shatteredpixeldungeon.utils.GLog; +import com.watabou.noosa.audio.Sample; +import com.watabou.utils.PathFinder; public class ScrollOfRemoveCurse extends InventoryScroll { @@ -43,6 +48,32 @@ public class ScrollOfRemoveCurse extends InventoryScroll { preferredBag = Belongings.Backpack.class; } + @Override + public void doRead() { + TormentedSpirit spirit = null; + for (int i : PathFinder.NEIGHBOURS8){ + if (Actor.findChar(curUser.pos+i) instanceof TormentedSpirit){ + spirit = (TormentedSpirit) Actor.findChar(curUser.pos+i); + } + } + if (spirit != null){ + identify(); + Sample.INSTANCE.play( Assets.Sounds.READ ); + readAnimation(); + + new Flare( 6, 32 ).show( curUser.sprite, 2f ); + + if (curUser.buff(Degrade.class) != null) { + Degrade.detach(curUser, Degrade.class); + } + + GLog.p(Messages.get(this, "spirit")); + spirit.cleanse(); + } else { + super.doRead(); + } + } + @Override protected boolean usableOnItem(Item item) { return uncursable(item); 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 0f24a8056..0bd44665e 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 @@ -116,7 +116,7 @@ public class DistortionTrap extends Trap{ case 2: switch (Random.Int(4)){ case 0: default: - Wraith.spawnAt(point); + Wraith.spawnAt(point, true); continue; //wraiths spawn themselves, no need to do more case 1: //yes it's intended that these are likely to die right away diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/TormentedSpiritSprite.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/TormentedSpiritSprite.java new file mode 100644 index 000000000..6b21927e6 --- /dev/null +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/TormentedSpiritSprite.java @@ -0,0 +1,57 @@ +/* + * 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.watabou.noosa.TextureFilm; + +public class TormentedSpiritSprite extends MobSprite { + + public TormentedSpiritSprite() { + super(); + + texture( Assets.Sprites.WRAITH ); + + TextureFilm frames = new TextureFilm( texture, 14, 15 ); + + int c = 9; + + idle = new Animation( 5, true ); + idle.frames( frames, c+0, c+1 ); + + run = new Animation( 10, true ); + run.frames( frames, c+0, c+1 ); + + attack = new Animation( 10, false ); + attack.frames( frames, c+0, c+2, c+3 ); + + die = new Animation( 8, false ); + die.frames( frames, c+0, c+4, c+5, c+6, c+7 ); + + play( idle ); + } + + @Override + public int blood() { + return 0x88BB0000; + } +}