v2.5.0: complete refactor of cursed wand effect logic
This commit is contained in:
+29
-3
@@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+8
-4
@@ -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 );
|
||||
}
|
||||
|
||||
+562
-366
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+6
-3
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user