diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/Elemental.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/Elemental.java index ff416bba5..6b5cb8dbd 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/Elemental.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/Elemental.java @@ -552,12 +552,38 @@ public abstract class Elemental extends Mob { @Override protected void meleeProc( Char enemy, int damage ) { - CursedWand.cursedEffect(null, this, enemy); + Ballistica aim = new Ballistica(pos, enemy.pos, Ballistica.STOP_TARGET); + //TODO shortcutting the fx seems fine for now but may cause problems with new cursed effects + //of course, not shortcutting it means actor ordering issues =S + CursedWand.randomValidEffect(null, this, aim, false).effect(null, this, aim, false); } - + + @Override + protected void zap() { + spend( 1f ); + + Invisibility.dispel(this); + Char enemy = this.enemy; + //skips accuracy check, always hits + rangedProc( enemy ); + + rangedCooldown = Random.NormalIntRange( 3, 5 ); + } + + @Override + public void onZapComplete() { + zap(); + //next(); triggers after wand effect + } + @Override protected void rangedProc( Char enemy ) { - CursedWand.cursedEffect(null, this, enemy); + CursedWand.cursedZap(null, this, new Ballistica(pos, enemy.pos, Ballistica.STOP_TARGET), new Callback() { + @Override + public void call() { + next(); + } + }); } } diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/scrolls/ScrollOfMirrorImage.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/scrolls/ScrollOfMirrorImage.java index 5be49d9c9..51650b221 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/scrolls/ScrollOfMirrorImage.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/scrolls/ScrollOfMirrorImage.java @@ -58,14 +58,18 @@ public class ScrollOfMirrorImage extends Scroll { readAnimation(); } - - //returns the number of images spawned + public static int spawnImages( Hero hero, int nImages ){ + return spawnImages( hero, hero.pos, nImages); + } + + //returns the number of images spawned + public static int spawnImages( Hero hero, int pos, int nImages ){ ArrayList respawnPoints = new ArrayList<>(); - for (int i = 0; i < PathFinder.NEIGHBOURS8.length; i++) { - int p = hero.pos + PathFinder.NEIGHBOURS8[i]; + for (int i = 0; i < PathFinder.NEIGHBOURS9.length; i++) { + int p = pos + PathFinder.NEIGHBOURS9[i]; if (Actor.findChar( p ) == null && Dungeon.level.passable[p]) { respawnPoints.add( p ); } diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/wands/CursedWand.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/wands/CursedWand.java index 895b7153b..3463e2d62 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/wands/CursedWand.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/wands/CursedWand.java @@ -88,419 +88,615 @@ import java.util.ArrayList; //helper class to contain all the cursed wand zapping logic, so the main wand class doesn't get huge. public class CursedWand { - private static float COMMON_CHANCE = 0.6f; - private static float UNCOMMON_CHANCE = 0.3f; - private static float RARE_CHANCE = 0.09f; - private static float VERY_RARE_CHANCE = 0.01f; - public static void cursedZap(final Item origin, final Char user, final Ballistica bolt, final Callback afterZap){ - cursedFX(user, bolt, new Callback() { + boolean positiveOnly = user == Dungeon.hero && Random.Float() < WondrousResin.positiveCurseEffectChance(); + CursedEffect effect = randomValidEffect(origin, user, bolt, positiveOnly); + //CursedEffect effect = new InterFloorTeleport(); + + effect.FX(origin, user, bolt, new Callback() { @Override public void call() { - if (cursedEffect(origin, user, bolt.collisionPos)){ - if (afterZap != null) afterZap.call(); - } + effect.effect(origin, user, bolt, positiveOnly); + afterZap.call(); } }); } public static void tryForWandProc( Char target, Item origin ){ - if (target != null && origin instanceof Wand){ + if (target != null && target != Dungeon.hero && origin instanceof Wand){ Wand.wandProc(target, origin.buffedLvl(), 1); } } - public static boolean cursedEffect(final Item origin, final Char user, final Char target){ - return cursedEffect(origin, user, target.pos); + //*** Cursed Effects *** + + public static abstract class CursedEffect { + + public boolean valid(Item origin, Char user, Ballistica bolt, boolean positiveOnly){ + return true; + } + + public void FX(final Item origin, final Char user, final Ballistica bolt, final Callback callback){ + MagicMissile.boltFromChar(user.sprite.parent, + MagicMissile.RAINBOW, + user.sprite, + bolt.collisionPos, + callback); + Sample.INSTANCE.play( Assets.Sounds.ZAP ); + } + + public abstract boolean effect(Item origin, Char user, Ballistica bolt, boolean positiveOnly); + } - public static boolean cursedEffect(final Item origin, final Char user, final int targetPos){ - switch (Random.chances(new float[]{COMMON_CHANCE, UNCOMMON_CHANCE, RARE_CHANCE, VERY_RARE_CHANCE})){ + // common/uncommon/rare/v.rare have a 60/30/9/1% chance respectively + private static float[] EFFECT_CAT_CHANCES = new float[]{60, 30, 9, 1}; + + public static CursedEffect randomEffect(){ + switch (Random.chances(EFFECT_CAT_CHANCES)){ case 0: default: - return commonEffect(origin, user, targetPos); + return randomCommonEffect(); case 1: - return uncommonEffect(origin, user, targetPos); + return randomUncommonEffect(); case 2: - return rareEffect(origin, user, targetPos); + return randomRareEffect(); case 3: - return veryRareEffect(origin, user, targetPos); + return randomVeryRareEffect(); } } - private static boolean commonEffect(final Item origin, final Char user, final int targetPos){ - boolean positiveOnly = user == Dungeon.hero && Random.Float() < WondrousResin.positiveCurseEffectChance(); - switch(Random.Int(4)){ - - //anti-entropy - //doesn't affect caster if positive only + public static CursedEffect randomValidEffect(Item origin, Char user, Ballistica bolt, boolean positiveOnly){ + switch (Random.chances(EFFECT_CAT_CHANCES)){ case 0: default: - Char target = Actor.findChar(targetPos); - if (Random.Int(2) == 0) { - if (target != null) Buff.affect(target, Burning.class).reignite(target); - if (!positiveOnly) Buff.affect(user, Frost.class, Frost.DURATION); + return randomValidCommonEffect(origin, user, bolt, positiveOnly); + case 1: + return randomValidUncommonEffect(origin, user, bolt, positiveOnly); + case 2: + return randomValidRareEffect(origin, user, bolt, positiveOnly); + case 3: + return randomValidVeryRareEffect(origin, user, bolt, positiveOnly); + } + } + + //********************** + //*** Common Effects *** + //********************** + + private static ArrayList COMMON_EFFECTS = new ArrayList<>(); + static { + COMMON_EFFECTS.add(new BurnAndFreeze()); + COMMON_EFFECTS.add(new SpawnRegrowth()); + COMMON_EFFECTS.add(new RandomTeleport()); + COMMON_EFFECTS.add(new RandomGas()); + } + + public static CursedEffect randomCommonEffect(){ + return Random.element(COMMON_EFFECTS); + } + + public static CursedEffect randomValidCommonEffect(Item origin, Char user, Ballistica bolt, boolean positiveOnly){ + CursedEffect effect; + do { + effect = Random.element(COMMON_EFFECTS); + } while (!effect.valid(origin, user, bolt, positiveOnly)); + return effect; + } + + public static class BurnAndFreeze extends CursedEffect { + @Override + public boolean effect(Item origin, Char user, Ballistica bolt, boolean positiveOnly) { + Char target = Actor.findChar(bolt.collisionPos); + //doesn't affect caster if positive only + if (Random.Int(2) == 0) { + if (target != null) Buff.affect(target, Burning.class).reignite(target); + if (!positiveOnly) Buff.affect(user, Frost.class, Frost.DURATION); + } else { + if (!positiveOnly)Buff.affect(user, Burning.class).reignite(user); + if (target != null) Buff.affect(target, Frost.class, Frost.DURATION); + } + tryForWandProc(target, origin); + return true; + } + } + + public static class SpawnRegrowth extends CursedEffect { + @Override + public boolean effect(Item origin, Char user, Ballistica bolt, boolean postiveOnly) { + if (Actor.findChar(bolt.collisionPos) == null){ + Dungeon.level.pressCell(bolt.collisionPos); + } + GameScene.add( Blob.seed(bolt.collisionPos, 30, Regrowth.class)); + tryForWandProc(Actor.findChar(bolt.collisionPos), origin); + return true; + } + } + + public static class RandomTeleport extends CursedEffect { + @Override + public boolean valid(Item origin, Char user, Ballistica bolt, boolean positiveOnly) { + Char target = Actor.findChar(bolt.collisionPos); + if (positiveOnly && (target == null || Char.hasProp(target, Char.Property.IMMOVABLE))){ + return false; + } + return true; + } + + //might be nice to have no fx if self teleports? + + @Override + public boolean effect(Item origin, Char user, Ballistica bolt, boolean positiveOnly) { + Char target = Actor.findChar( bolt.collisionPos ); + //can only teleport target if positive only + if (target != null && !Char.hasProp(target, Char.Property.IMMOVABLE) && (positiveOnly || Random.Int(2) == 0)){ + ScrollOfTeleportation.teleportChar(target); + tryForWandProc(target, origin); + return true; + } else { + if (positiveOnly || user == null || Char.hasProp(user, Char.Property.IMMOVABLE)){ + return false; } else { - if (!positiveOnly)Buff.affect(user, Burning.class).reignite(user); - if (target != null) Buff.affect(target, Frost.class, Frost.DURATION); + ScrollOfTeleportation.teleportChar(user); + return true; + } + } + } + } + + public static class RandomGas extends CursedEffect { + @Override + public boolean effect(Item origin, Char user, Ballistica bolt, boolean positiveOnly) { + Sample.INSTANCE.play( Assets.Sounds.GAS ); + tryForWandProc(Actor.findChar(bolt.collisionPos), origin); + if (Actor.findChar(bolt.collisionPos) == null){ + Dungeon.level.pressCell(bolt.collisionPos); + } + switch (Random.Int(3)) { + case 0: default: + GameScene.add( Blob.seed( bolt.collisionPos, 800, ConfusionGas.class ) ); + return true; + case 1: + GameScene.add( Blob.seed( bolt.collisionPos, 500, ToxicGas.class ) ); + return true; + case 2: + GameScene.add( Blob.seed( bolt.collisionPos, 200, ParalyticGas.class ) ); + return true; + } + } + } + + //************************ + //*** Uncommon Effects *** + //************************ + + private static ArrayList UNCOMMON_EFFECTS = new ArrayList<>(); + static { + UNCOMMON_EFFECTS.add(new RandomPlant()); + UNCOMMON_EFFECTS.add(new HealthTransfer()); + UNCOMMON_EFFECTS.add(new Explosion()); + UNCOMMON_EFFECTS.add(new ShockAndRecharge()); + } + + public static CursedEffect randomUncommonEffect(){ + return Random.element(UNCOMMON_EFFECTS); + } + + public static CursedEffect randomValidUncommonEffect(Item origin, Char user, Ballistica bolt, boolean positiveOnly){ + CursedEffect effect; + do { + effect = Random.element(UNCOMMON_EFFECTS); + } while (!effect.valid(origin, user, bolt, positiveOnly)); + return effect; + } + + public static class RandomPlant extends CursedEffect { + + @Override + public boolean valid(Item origin, Char user, Ballistica bolt, boolean positiveOnly) { + int pos = bolt.collisionPos; + + if (Dungeon.level.map[pos] != Terrain.ALCHEMY + && !Dungeon.level.pit[pos] + && Dungeon.level.traps.get(pos) == null + && !Dungeon.isChallenged(Challenges.NO_HERBALISM)) { + return true; + } else { + return false; + } + } + + @Override + public boolean effect(Item origin, Char user, Ballistica bolt, boolean positiveOnly) { + if (valid(origin, user, bolt, positiveOnly)) { + Dungeon.level.plant((Plant.Seed) Generator.randomUsingDefaults(Generator.Category.SEED), bolt.collisionPos); + return true; + } else { + return false; + } + } + } + + public static class HealthTransfer extends CursedEffect { + + @Override + public boolean valid(Item origin, Char user, Ballistica bolt, boolean positiveOnly) { + return Actor.findChar( bolt.collisionPos ) != null; + } + + @Override + public boolean effect(Item origin, Char user, Ballistica bolt, boolean positiveOnly) { + final Char target = Actor.findChar( bolt.collisionPos ); + if (target != null) { + int damage = Dungeon.scalingDepth() * 2; + Char toHeal, toDamage; + + //can only harm target if positive only + if (positiveOnly || Random.Int(2) == 0){ + toHeal = user; + toDamage = target; + } else { + toHeal = target; + toDamage = user; + } + toHeal.HP = Math.min(toHeal.HT, toHeal.HP + damage); + toHeal.sprite.emitter().burst(Speck.factory(Speck.HEALING), 3); + toHeal.sprite.showStatusWithIcon( CharSprite.POSITIVE, Integer.toString(damage), FloatingText.HEALING ); + + toDamage.damage(damage, new CursedWand()); + toDamage.sprite.emitter().start(ShadowParticle.UP, 0.05f, 10); + + if (toDamage == Dungeon.hero){ + Sample.INSTANCE.play(Assets.Sounds.CURSED); + if (!toDamage.isAlive()) { + if (user == Dungeon.hero && origin != null) { + Badges.validateDeathFromFriendlyMagic(); + Dungeon.fail( origin ); + GLog.n( Messages.get( CursedWand.class, "ondeath", origin.name() ) ); + } else { + Badges.validateDeathFromEnemyMagic(); + Dungeon.fail( toHeal ); + } + } + } else { + Sample.INSTANCE.play(Assets.Sounds.BURNING); } tryForWandProc(target, origin); return true; - - //spawns some regrowth - case 1: - GameScene.add( Blob.seed(targetPos, 30, Regrowth.class)); - tryForWandProc(Actor.findChar(targetPos), origin); - return true; - - //random teleportation - //can only teleport enemy if positive only - case 2: - if(!positiveOnly && Random.Int(2) == 0) { - if (user != null && !user.properties().contains(Char.Property.IMMOVABLE)) { - ScrollOfTeleportation.teleportChar(user); - } else { - return cursedEffect(origin, user, targetPos); - } - } else { - Char ch = Actor.findChar( targetPos ); - if (ch != null && !ch.properties().contains(Char.Property.IMMOVABLE)) { - ScrollOfTeleportation.teleportChar(ch); - tryForWandProc(ch, origin); - } else { - return cursedEffect(origin, user, targetPos); - } - } - return true; - - //random gas at location - case 3: - Sample.INSTANCE.play( Assets.Sounds.GAS ); - tryForWandProc(Actor.findChar(targetPos), origin); - switch (Random.Int(3)) { - case 0: default: - GameScene.add( Blob.seed( targetPos, 800, ConfusionGas.class ) ); - return true; - case 1: - GameScene.add( Blob.seed( targetPos, 500, ToxicGas.class ) ); - return true; - case 2: - GameScene.add( Blob.seed( targetPos, 200, ParalyticGas.class ) ); - return true; - } + } else { + return false; + } } - } - private static boolean uncommonEffect(final Item origin, final Char user, final int targetPos){ - boolean positiveOnly = user == Dungeon.hero && Random.Float() < WondrousResin.positiveCurseEffectChance(); - switch(Random.Int(4)){ + public static class Explosion extends CursedEffect { + @Override + public boolean effect(Item origin, Char user, Ballistica bolt, boolean positiveOnly) { + new Bomb.ConjuredBomb().explode(bolt.collisionPos); + tryForWandProc(Actor.findChar(bolt.collisionPos), origin); + return true; + } + } - //Random plant - case 0: default: - int pos = targetPos; - - if (Dungeon.level.map[pos] != Terrain.ALCHEMY - && !Dungeon.level.pit[pos] - && Dungeon.level.traps.get(pos) == null - && !Dungeon.isChallenged(Challenges.NO_HERBALISM)) { - Dungeon.level.plant((Plant.Seed) Generator.randomUsingDefaults(Generator.Category.SEED), pos); - tryForWandProc(Actor.findChar(pos), origin); - } else { - return cursedEffect(origin, user, targetPos); - } - - return true; - - //Health transfer - //can only harm enemy if positive only - case 1: - final Char target = Actor.findChar( targetPos ); - if (target != null) { - int damage = Dungeon.scalingDepth() * 2; - Char toHeal, toDamage; - - if (positiveOnly || Random.Int(2) == 0){ - toHeal = user; - toDamage = target; - } else { - toHeal = target; - toDamage = user; - } - toHeal.HP = Math.min(toHeal.HT, toHeal.HP + damage); - toHeal.sprite.emitter().burst(Speck.factory(Speck.HEALING), 3); - toHeal.sprite.showStatusWithIcon( CharSprite.POSITIVE, Integer.toString(damage), FloatingText.HEALING ); - - toDamage.damage(damage, new CursedWand()); - toDamage.sprite.emitter().start(ShadowParticle.UP, 0.05f, 10); - - if (toDamage == Dungeon.hero){ - Sample.INSTANCE.play(Assets.Sounds.CURSED); - if (!toDamage.isAlive()) { - if (user == Dungeon.hero && origin != null) { - Badges.validateDeathFromFriendlyMagic(); - Dungeon.fail( origin ); - GLog.n( Messages.get( CursedWand.class, "ondeath", origin.name() ) ); - } else { - Badges.validateDeathFromEnemyMagic(); - Dungeon.fail( toHeal ); - } - } - } else { - Sample.INSTANCE.play(Assets.Sounds.BURNING); - } - tryForWandProc(target, origin); - } else { - return cursedEffect(origin, user, targetPos); - } - return true; - - //Bomb explosion - case 2: - new Bomb.ConjuredBomb().explode(targetPos); - tryForWandProc(Actor.findChar(targetPos), origin); - return true; - - //shock and recharge + public static class ShockAndRecharge extends CursedEffect { + @Override + public boolean effect(Item origin, Char user, Ballistica bolt, boolean positiveOnly) { //no shock if positive only - case 3: - if (!positiveOnly) new ShockingTrap().set( user.pos ).activate(); - Buff.prolong(user, Recharging.class, Recharging.DURATION); - ScrollOfRecharging.charge(user); - SpellSprite.show(user, SpellSprite.CHARGE); - return true; - } - - } - - private static boolean rareEffect(final Item origin, final Char user, final int targetPos){ - boolean positiveOnly = user == Dungeon.hero && Random.Float() < WondrousResin.positiveCurseEffectChance(); - switch(Random.Int(4)){ - - //sheep transformation - case 0: default: - - Char ch = Actor.findChar( targetPos ); - if (ch != null && !(ch instanceof Hero) - //ignores bosses, questgivers, rat king, etc. - && !ch.properties().contains(Char.Property.BOSS) - && !ch.properties().contains(Char.Property.MINIBOSS) - && !(ch instanceof NPC && ch.alignment == Char.Alignment.NEUTRAL)){ - Sheep sheep = new Sheep(); - sheep.lifespan = 10; - sheep.pos = ch.pos; - ch.destroy(); - ch.sprite.killAndErase(); - Dungeon.level.mobs.remove(ch); - TargetHealthIndicator.instance.target(null); - GameScene.add(sheep); - CellEmitter.get(sheep.pos).burst(Speck.factory(Speck.WOOL), 4); - Sample.INSTANCE.play(Assets.Sounds.PUFF); - Sample.INSTANCE.play(Assets.Sounds.SHEEP); - Dungeon.level.occupyCell(sheep); - } else { - return cursedEffect(origin, user, targetPos); - } - return true; - - //curses! - //or hexes target if positive only - case 1: - if (positiveOnly){ - ch = Actor.findChar( targetPos ); - if (ch != null){ - Buff.affect(ch, Hex.class, Hex.DURATION); - } - return true; - } - if (user instanceof Hero) { - CursingTrap.curse( (Hero) user ); - } else { - return cursedEffect(origin, user, targetPos); - } - return true; - - //inter-level teleportation - //of scroll of teleportation if positive only, or inter-floor teleport disallowed - case 2: - if (!positiveOnly && Dungeon.depth > 1 && Dungeon.interfloorTeleportAllowed() && user == Dungeon.hero) { - - //each depth has 1 more weight than the previous depth. - float[] depths = new float[Dungeon.depth-1]; - for (int i = 1; i < Dungeon.depth; i++) depths[i-1] = i; - int depth = 1+Random.chances(depths); - - Level.beforeTransition(); - InterlevelScene.mode = InterlevelScene.Mode.RETURN; - InterlevelScene.returnDepth = depth; - InterlevelScene.returnBranch = 0; - InterlevelScene.returnPos = -1; - Game.switchScene(InterlevelScene.class); - - } else { - ScrollOfTeleportation.teleportChar(user); - - } - return true; - - //summon monsters - //or mirror images if positive only - case 3: - if (positiveOnly && user == Dungeon.hero){ - ScrollOfMirrorImage.spawnImages(Dungeon.hero, 2); - } else { - new SummoningTrap().set(targetPos).activate(); - } - return true; + if (!positiveOnly) new ShockingTrap().set( user.pos ).activate(); + Buff.prolong(user, Recharging.class, Recharging.DURATION); + ScrollOfRecharging.charge(user); + SpellSprite.show(user, SpellSprite.CHARGE); + return true; } } - private static boolean veryRareEffect(final Item origin, final Char user, final int targetPos){ - boolean positiveOnly = user == Dungeon.hero && Random.Float() < WondrousResin.positiveCurseEffectChance(); - switch( Random.Int(4) ){ + //******************** + //*** Rare Effects *** + //******************** - //great forest fire! - //only grass, no fire, if positive only - case 0: default: - for (int i = 0; i < Dungeon.level.length(); i++){ - GameScene.add( Blob.seed(i, 15, Regrowth.class)); - } + private static ArrayList RARE_EFFECTS = new ArrayList<>(); + static { + RARE_EFFECTS.add(new SheepPolymorph()); + RARE_EFFECTS.add(new CurseEquipment()); + RARE_EFFECTS.add(new InterFloorTeleport()); + RARE_EFFECTS.add(new SummonMonsters()); + } - new Flare(8, 32).color(0xFFFF66, true).show(user.sprite, 2f); - Sample.INSTANCE.play(Assets.Sounds.TELEPORT); - GLog.p(Messages.get(CursedWand.class, "grass")); - if (!positiveOnly) { - GLog.w(Messages.get(CursedWand.class, "fire")); - do { - GameScene.add(Blob.seed(Dungeon.level.randomDestination(null), 10, Fire.class)); - } while (Random.Int(5) != 0); - } + public static CursedEffect randomRareEffect(){ + return Random.element(RARE_EFFECTS); + } + + public static CursedEffect randomValidRareEffect(Item origin, Char user, Ballistica bolt, boolean positiveOnly){ + CursedEffect effect; + do { + effect = Random.element(RARE_EFFECTS); + } while (!effect.valid(origin, user, bolt, positiveOnly)); + return effect; + } + + public static class SheepPolymorph extends CursedEffect { + + @Override + public boolean valid(Item origin, Char user, Ballistica bolt, boolean positiveOnly) { + Char ch = Actor.findChar( bolt.collisionPos ); + if (ch != null && !(ch instanceof Hero) + //ignores bosses, questgivers, rat king, etc. + && !ch.properties().contains(Char.Property.BOSS) + && !ch.properties().contains(Char.Property.MINIBOSS) + && !(ch instanceof NPC && ch.alignment == Char.Alignment.NEUTRAL)){ return true; + } else { + return false; + } + } - //golden mimic - //mimic is enthralled if positive only - case 1: + @Override + public boolean effect(Item origin, Char user, Ballistica bolt, boolean positiveOnly) { + if (valid(origin, user, bolt, positiveOnly)){ + Char ch = Actor.findChar( bolt.collisionPos ); + Sheep sheep = new Sheep(); + sheep.lifespan = 10; + sheep.pos = ch.pos; + ch.destroy(); + ch.sprite.killAndErase(); + Dungeon.level.mobs.remove(ch); + TargetHealthIndicator.instance.target(null); + GameScene.add(sheep); + CellEmitter.get(sheep.pos).burst(Speck.factory(Speck.WOOL), 4); + Sample.INSTANCE.play(Assets.Sounds.PUFF); + Sample.INSTANCE.play(Assets.Sounds.SHEEP); + Dungeon.level.occupyCell(sheep); + return true; + } else { + return false; + } + } + } - Char ch = Actor.findChar(targetPos); - int spawnCell = targetPos; + public static class CurseEquipment extends CursedEffect { + + @Override + public boolean effect(Item origin, Char user, Ballistica bolt, boolean positiveOnly) { + //hexes target if positive only or user isn't hero + if (positiveOnly || !(user instanceof Hero)){ + Char ch = Actor.findChar( bolt.collisionPos ); if (ch != null){ - ArrayList candidates = new ArrayList(); - for (int n : PathFinder.NEIGHBOURS8) { - int cell = targetPos + n; - if (Dungeon.level.passable[cell] && Actor.findChar( cell ) == null) { - candidates.add( cell ); - } - } - if (!candidates.isEmpty()){ - spawnCell = Random.element(candidates); - } else { - return cursedEffect(origin, user, targetPos); - } + Buff.affect(ch, Hex.class, Hex.DURATION); } - - Mimic mimic = Mimic.spawnAt(spawnCell, GoldenMimic.class, false); - mimic.stopHiding(); - mimic.alignment = Char.Alignment.ENEMY; - //play vfx/sfx manually as mimic isn't in the scene yet - Sample.INSTANCE.play(Assets.Sounds.MIMIC, 1, 0.85f); - CellEmitter.get(mimic.pos).burst(Speck.factory(Speck.STAR), 10); - mimic.items.clear(); - GameScene.add(mimic); - - if (positiveOnly){ - Buff.affect(mimic, ScrollOfSirensSong.Enthralled.class); - } else { - Item reward; - do { - reward = Generator.randomUsingDefaults(Random.oneOf(Generator.Category.WEAPON, Generator.Category.ARMOR, - Generator.Category.RING, Generator.Category.WAND)); - } while (reward.level() < 1); - mimic.items.add(reward); - } - - Dungeon.level.occupyCell(mimic); - return true; - - //appears to crash the game (actually just closes it) - case 2: - - try { - Dungeon.saveAll(); - if(Messages.lang() != Languages.ENGLISH){ - //Don't bother doing this joke to none-english speakers, I doubt it would translate. - return cursedEffect(origin, user, targetPos); - } else { - ShatteredPixelDungeon.runOnRenderThread( - new Callback() { - @Override - public void call() { - GameScene.show( - new WndOptions(Icons.get(Icons.WARNING), - "CURSED WAND ERROR", - "this application will now self-destruct", - "abort", - "retry", - "fail") { - - @Override - protected void onSelect(int index) { - Game.instance.finish(); - } - - @Override - public void onBackPressed() { - //do nothing - } - } - ); - } - } - ); - return false; - } - } catch(IOException e){ - ShatteredPixelDungeon.reportException(e); - //maybe don't kill the game if the save failed. - return cursedEffect(origin, user, targetPos); - } - - //random transmogrification - //or triggers metamorph effect if positive only - case 3: - if (positiveOnly){ - GameScene.show(new ScrollOfMetamorphosis.WndMetamorphChoose()); - return true; - } - - //skips this effect if there is no item to transmogrify - if (origin == null || user != Dungeon.hero || !Dungeon.hero.belongings.contains(origin)){ - return cursedEffect(origin, user, targetPos); - } - origin.detach(Dungeon.hero.belongings.backpack); - Item result; - do { - result = Generator.randomUsingDefaults(Random.oneOf(Generator.Category.WEAPON, Generator.Category.ARMOR, - Generator.Category.RING, Generator.Category.ARTIFACT)); - } while (result.cursed); - if (result.isUpgradable()) result.upgrade(); - result.cursed = result.cursedKnown = true; - if (origin instanceof Wand){ - GLog.w( Messages.get(CursedWand.class, "transmogrify_wand") ); - } else { - GLog.w( Messages.get(CursedWand.class, "transmogrify_other") ); - } - Dungeon.level.drop(result, user.pos).sprite.drop(); + } else { + CursingTrap.curse( (Hero) user ); return true; + } } } - private static void cursedFX(final Char user, final Ballistica bolt, final Callback callback){ - MagicMissile.boltFromChar( user.sprite.parent, - MagicMissile.RAINBOW, - user.sprite, - bolt.collisionPos, - callback); - Sample.INSTANCE.play( Assets.Sounds.ZAP ); + public static class InterFloorTeleport extends CursedEffect { + + @Override + public void FX(Item origin, Char user, Ballistica bolt, Callback callback) { + callback.call(); //no vfx + } + + @Override + public boolean effect(Item origin, Char user, Ballistica bolt, boolean positiveOnly) { + if (!positiveOnly && Dungeon.depth > 1 && Dungeon.interfloorTeleportAllowed() && user == Dungeon.hero) { + + //each depth has 1 more weight than the previous depth. + float[] depths = new float[Dungeon.depth-1]; + for (int i = 1; i < Dungeon.depth; i++) depths[i-1] = i; + int depth = 1+Random.chances(depths); + + Level.beforeTransition(); + InterlevelScene.mode = InterlevelScene.Mode.RETURN; + InterlevelScene.returnDepth = depth; + InterlevelScene.returnBranch = 0; + InterlevelScene.returnPos = -1; + Game.switchScene(InterlevelScene.class); + + //scroll of teleportation if positive only, or inter-floor teleport disallowed + } else { + ScrollOfTeleportation.teleportChar(user); + + } + return true; + } + } + + public static class SummonMonsters extends CursedEffect { + + @Override + public boolean effect(Item origin, Char user, Ballistica bolt, boolean positiveOnly) { + //mirror images if positive only and user is hero + if (positiveOnly && user == Dungeon.hero){ + ScrollOfMirrorImage.spawnImages(Dungeon.hero, bolt.collisionPos, 2); + } else { + new SummoningTrap().set(bolt.collisionPos).activate(); + } + return true; + } + } + + //************************* + //*** Very Rare Effects *** + //************************* + + private static ArrayList VERY_RARE_EFFECTS = new ArrayList<>(); + static { + VERY_RARE_EFFECTS.add(new ForestFire()); + VERY_RARE_EFFECTS.add(new SpawnGoldenMimic()); + VERY_RARE_EFFECTS.add(new AbortRetryFail()); + VERY_RARE_EFFECTS.add(new RandomTransmogrify()); + } + + public static CursedEffect randomVeryRareEffect(){ + return Random.element(VERY_RARE_EFFECTS); + } + + public static CursedEffect randomValidVeryRareEffect(Item origin, Char user, Ballistica bolt, boolean positiveOnly){ + CursedEffect effect; + do { + effect = Random.element(VERY_RARE_EFFECTS); + } while (!effect.valid(origin, user, bolt, positiveOnly)); + return effect; + } + + public static class ForestFire extends CursedEffect { + @Override + public boolean effect(Item origin, Char user, Ballistica bolt, boolean positiveOnly) { + for (int i = 0; i < Dungeon.level.length(); i++){ + GameScene.add( Blob.seed(i, 15, Regrowth.class)); + } + + new Flare(8, 32).color(0xFFFF66, true).show(user.sprite, 2f); + Sample.INSTANCE.play(Assets.Sounds.TELEPORT); + GLog.p(Messages.get(CursedWand.class, "grass")); + //only grass, no fire, if positive only + if (!positiveOnly) { + GLog.w(Messages.get(CursedWand.class, "fire")); + do { + GameScene.add(Blob.seed(Dungeon.level.randomDestination(null), 10, Fire.class)); + } while (Random.Int(5) != 0); + } + return true; + } + } + + public static class SpawnGoldenMimic extends CursedEffect { + @Override + public boolean effect(Item origin, Char user, Ballistica bolt, boolean positiveOnly) { + Char ch = Actor.findChar(bolt.collisionPos); + int spawnCell = bolt.collisionPos; + if (ch != null){ + ArrayList candidates = new ArrayList(); + for (int n : PathFinder.NEIGHBOURS8) { + int cell = bolt.collisionPos + n; + if (Dungeon.level.passable[cell] && Actor.findChar( cell ) == null) { + candidates.add( cell ); + } + } + if (!candidates.isEmpty()){ + spawnCell = Random.element(candidates); + } else { + return false; + } + } + + Mimic mimic = Mimic.spawnAt(spawnCell, GoldenMimic.class, false); + mimic.stopHiding(); + mimic.alignment = Char.Alignment.ENEMY; + //play vfx/sfx manually as mimic isn't in the scene yet + Sample.INSTANCE.play(Assets.Sounds.MIMIC, 1, 0.85f); + CellEmitter.get(mimic.pos).burst(Speck.factory(Speck.STAR), 10); + mimic.items.clear(); + GameScene.add(mimic); + + //mimic is enthralled, but also contains no extra reward, if positive only + if (positiveOnly){ + Buff.affect(mimic, ScrollOfSirensSong.Enthralled.class); + } else { + Item reward; + do { + reward = Generator.randomUsingDefaults(Random.oneOf(Generator.Category.WEAPON, Generator.Category.ARMOR, + Generator.Category.RING, Generator.Category.WAND)); + } while (reward.level() < 1); + mimic.items.add(reward); + } + + Dungeon.level.occupyCell(mimic); + + return true; + } + } + + public static class AbortRetryFail extends CursedEffect { + + @Override + public boolean effect(Item origin, Char user, Ballistica bolt, boolean positiveOnly) { + //appears to crash the game (actually just closes it) + try { + Dungeon.saveAll(); + if(Messages.lang() != Languages.ENGLISH){ + //Don't bother doing this joke to none-english speakers, I doubt it would translate. + //we still consider the effect valid here though as it's cosmetic anyway + return false; + } else { + ShatteredPixelDungeon.runOnRenderThread( + new Callback() { + @Override + public void call() { + GameScene.show( + new WndOptions(Icons.get(Icons.WARNING), + "CURSED WAND ERROR", + "this application will now self-destruct", + "abort", + "retry", + "fail") { + + @Override + protected void onSelect(int index) { + Game.instance.finish(); + } + + @Override + public void onBackPressed() { + //do nothing + } + } + ); + } + } + ); + return false; + } + } catch(IOException e){ + ShatteredPixelDungeon.reportException(e); + //maybe don't kill the game if the save failed, just do nothing + return false; + } + } + } + + public static class RandomTransmogrify extends CursedEffect { + + @Override + public boolean valid(Item origin, Char user, Ballistica bolt, boolean positiveOnly) { + if (positiveOnly){ + return true; + } else if (origin == null || user != Dungeon.hero || !Dungeon.hero.belongings.contains(origin)){ + return false; + } else { + return true; + } + } + + @Override + public boolean effect(Item origin, Char user, Ballistica bolt, boolean positiveOnly) { + //triggers metamorph effect if positive only + if (positiveOnly){ + GameScene.show(new ScrollOfMetamorphosis.WndMetamorphChoose()); + return true; + } + + //skips this effect if there is no item to transmogrify + if (origin == null || user != Dungeon.hero || !Dungeon.hero.belongings.contains(origin)){ + return false; + } + origin.detach(Dungeon.hero.belongings.backpack); + Item result; + do { + result = Generator.randomUsingDefaults(Random.oneOf(Generator.Category.WEAPON, Generator.Category.ARMOR, + Generator.Category.RING, Generator.Category.ARTIFACT)); + } while (result.cursed); + if (result.isUpgradable()) result.upgrade(); + result.cursed = result.cursedKnown = true; + if (origin instanceof Wand){ + GLog.w( Messages.get(CursedWand.class, "transmogrify_wand") ); + } else { + GLog.w( Messages.get(CursedWand.class, "transmogrify_other") ); + } + Dungeon.level.drop(result, user.pos).sprite.drop(); + return true; + } } } diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/ElementalSprite.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/ElementalSprite.java index e1e331f18..8436b4ec6 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/ElementalSprite.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/ElementalSprite.java @@ -232,10 +232,13 @@ public abstract class ElementalSprite extends MobSprite { public static class Chaos extends ElementalSprite { - { - boltType = MagicMissile.RAINBOW; + @Override + public void zap(int cell) { + zap( cell, null ); //effectively super.super.zap + //relies on cursed wand for effects + ((Elemental)ch).onZapComplete(); } - + @Override protected int texOffset() { return 56;