v2.5.0: complete refactor of cursed wand effect logic

This commit is contained in:
Evan Debenham
2024-08-12 17:12:10 -04:00
parent 70053d933e
commit 70df007bcf
4 changed files with 605 additions and 376 deletions

View File

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

View File

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

View File

@@ -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<CursedEffect> 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<CursedEffect> 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<CursedEffect> 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<Integer> candidates = new ArrayList<Integer>();
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<CursedEffect> 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<Integer> candidates = new ArrayList<Integer>();
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;
}
}
}

View File

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