v2.1.0: added a new exotic enemy: tormented spirits

This commit is contained in:
Evan Debenham
2023-04-27 17:37:38 -04:00
parent 7c1e813bea
commit 67132b929f
13 changed files with 195 additions and 14 deletions

View File

@@ -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.

View File

@@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 296 B

After

Width:  |  Height:  |  Size: 491 B

View File

@@ -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]) {

View File

@@ -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();

View File

@@ -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 <http://www.gnu.org/licenses/>
*/
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 );
}
}

View File

@@ -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 {

View File

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

View File

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

View File

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

View File

@@ -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);

View File

@@ -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

View File

@@ -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 <http://www.gnu.org/licenses/>
*/
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;
}
}