v0.9.3: implemented the warp beacon ability

This commit is contained in:
Evan Debenham
2021-05-20 21:29:18 -04:00
parent 72b2c3ece6
commit 3314e607d0
7 changed files with 262 additions and 53 deletions

View File

@@ -354,9 +354,17 @@ actors.hero.abilities.mage.wildmagic.name=wild magic
actors.hero.abilities.mage.wildmagic.no_wands=You have no wands to zap with!
actors.hero.abilities.mage.wildmagic.short_desc=The Mage unleashes the _Wild Magic_ contained in his wands, randomly firing them multiple times at a chosen target over a single turn.
actors.hero.abilities.mage.wildmagic.desc=TODO
actors.hero.abilities.mage.magicalchains.name=magical chains
actors.hero.abilities.mage.magicalchains.short_desc=The Mage throws out _Magical Chains_, which affect every enemy he has used magic on recently.
actors.hero.abilities.mage.magicalchains.desc=TODO
actors.hero.abilities.mage.warpbeacon.name=warp beacon
actors.hero.abilities.mage.warpbeacon.depths=You can't warp between depths!
actors.hero.abilities.mage.warpbeacon.locked_floor=You cannot leave a locked floor!
actors.hero.abilities.mage.warpbeacon.too_far=That location is too far!
actors.hero.abilities.mage.warpbeacon.invalid_beacon=You can't place a beacon there!
actors.hero.abilities.mage.warpbeacon.window_desc=Your beacon is currently placed on floor %d.
actors.hero.abilities.mage.warpbeacon.window_tele=teleport to beacon
actors.hero.abilities.mage.warpbeacon.window_clear=clear beacon
actors.hero.abilities.mage.warpbeacon.window_cancel=cancel
actors.hero.abilities.mage.warpbeacon.short_desc=The Mage places a _Warping Beacon_ at his current location, which he can instantly teleport back to.
actors.hero.abilities.mage.warpbeacon.desc=TODO
actors.hero.abilities.rogue.smokebomb.name=smoke bomb
actors.hero.abilities.rogue.smokebomb.fov=You can only jump to an empty location in your field of view
actors.hero.abilities.rogue.smokebomb.prompt=Choose a location to jump to
@@ -570,12 +578,12 @@ actors.hero.talent.fire_everything.desc=_+1:_ Wild magic now fires _5 times_, up
actors.hero.talent.conserved_magic.title=conserved magic
actors.hero.talent.conserved_magic.desc=_+1:_ Each zap from wild magic now uses _0.71 charges_, instead of 1.\n\n_+2:_ Each zap from wild magic now uses _0.50 charges_, instead of 1.\n\n_+3:_ Each zap from wild magic now uses _0.35 charges_, instead of 1.\n\n_+4:_ Each zap from wild magic now uses _0.25 charges_, instead of 1.
actors.hero.talent.mage_3_1.title=TODO NAME
actors.hero.talent.mage_3_1.desc=_+1:_ \n\n_+2:_ \n\n_+3:_ \n\n_+4:_
actors.hero.talent.mage_3_2.title=TODO NAME
actors.hero.talent.mage_3_2.desc=TODO DESC
actors.hero.talent.mage_3_3.title=TODO NAME
actors.hero.talent.mage_3_3.desc=TODO DESC
actors.hero.talent.telefrag.title=Telefrag
actors.hero.talent.telefrag.desc=_+1:_ If the Mage warps into another character he deals _10-15 damage_ to it, but also takes _5 damage_.\n\n_+2:_ If the Mage warps into another character he deals _20-30 damage_ to it, but also takes _8 damage_.\n\n_+3:_ If the Mage warps into another character he deals _30-45 damage_ to it, but also takes _12 damage_.\n\n_+4:_ If the Mage warps into another character he deals _40-60 damage_ to it, but also takes _15 damage_.\n\nThe hero cannot be killed by this talent, the self damage can be reduced by magic resisting effects.
actors.hero.talent.remote_beacon.title=Remote Beacon
actors.hero.talent.remote_beacon.desc=_+1:_ The Mage can place a beacon at any location within _3 tiles_.\n\n_+2:_ The Mage can place a beacon at any location within _6 tiles_.\n\n_+3:_ The Mage can place a beacon at any location within _9 tiles_.\n\n_+4:_ The Mage can place a beacon at any location within _12 tiles_.\n\nThe Mage cannot place beacons in locations that are inaccessible
actors.hero.talent.longrange_warp.title=Longrange Warp
actors.hero.talent.longrange_warp.desc=_+1:_ The Mage can now warp between floors, at _150% charge cost_.\n\n_+2:_ The Mage can now warp between floors, at _117% charge cost_.\n\n_+3:_ The Mage can now warp between floors, at _83% charge cost_.\n\n_+4:_ The Mage can now warp between floors, at _50% charge cost_.\n\nThe Mage cannot use longrange warp to leave a locked floor.
#rogue
actors.hero.talent.cached_rations.title=cached rations

View File

@@ -31,7 +31,7 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.huntress.N
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.huntress.SpiritHawk;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.huntress.SpectralBlades;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.mage.WildMagic;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.mage.MagicalChains;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.mage.WarpBeacon;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.mage.ElementalBlast;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.rogue.DeathMark;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.rogue.ShadowClone;
@@ -213,7 +213,7 @@ public enum HeroClass {
case WARRIOR: default:
return new ArmorAbility[]{new HeroicLeap(), new Shockwave(), new Endure()};
case MAGE:
return new ArmorAbility[]{new ElementalBlast(), new WildMagic(), new MagicalChains()};
return new ArmorAbility[]{new ElementalBlast(), new WildMagic(), new WarpBeacon()};
case ROGUE:
return new ArmorAbility[]{new SmokeBomb(), new DeathMark(), new ShadowClone()};
case HUNTRESS:

View File

@@ -102,8 +102,8 @@ public enum Talent {
BLAST_RADIUS(49, 4), ELEMENTAL_POWER(50, 4), REACTIVE_BARRIER(51, 4),
//Wild Magic T4
WILD_POWER(52, 4), FIRE_EVERYTHING(53, 4), CONSERVED_MAGIC(54, 4),
//??? T4
MAGE_3_1(55, 4), MAGE_3_2(56, 4), MAGE_3_3(57, 4),
//Warp Beacon T4
TELEFRAG(55, 4), REMOTE_BEACON(56, 4), LONGRANGE_WARP(57, 4),
//Rogue T1
CACHED_RATIONS(64), THIEFS_INTUITION(65), SUCKER_PUNCH(66), PROTECTIVE_SHADOWS(67),

View File

@@ -1,40 +0,0 @@
/*
* Pixel Dungeon
* Copyright (C) 2012-2015 Oleg Dolya
*
* Shattered Pixel Dungeon
* Copyright (C) 2014-2021 Evan Debenham
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.mage;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Talent;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.ArmorAbility;
import com.shatteredpixel.shatteredpixeldungeon.items.armor.ClassArmor;
public class MagicalChains extends ArmorAbility {
@Override
protected void activate(ClassArmor armor, Hero hero, Integer target) {
//TODO
}
@Override
public Talent[] talents() {
return new Talent[]{Talent.MAGE_3_1, Talent.MAGE_3_2, Talent.MAGE_3_3, Talent.HEROIC_ENERGY};
}
}

View File

@@ -0,0 +1,237 @@
/*
* Pixel Dungeon
* Copyright (C) 2012-2015 Oleg Dolya
*
* Shattered Pixel Dungeon
* Copyright (C) 2014-2021 Evan Debenham
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.mage;
import com.shatteredpixel.shatteredpixeldungeon.Assets;
import com.shatteredpixel.shatteredpixeldungeon.Dungeon;
import com.shatteredpixel.shatteredpixeldungeon.actors.Actor;
import com.shatteredpixel.shatteredpixeldungeon.actors.Char;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.LockedFloor;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Talent;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.ArmorAbility;
import com.shatteredpixel.shatteredpixeldungeon.effects.CellEmitter;
import com.shatteredpixel.shatteredpixeldungeon.effects.MagicMissile;
import com.shatteredpixel.shatteredpixeldungeon.effects.Pushing;
import com.shatteredpixel.shatteredpixeldungeon.items.armor.ClassArmor;
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.EtherealChains;
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.TimekeepersHourglass;
import com.shatteredpixel.shatteredpixeldungeon.items.scrolls.ScrollOfTeleportation;
import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
import com.shatteredpixel.shatteredpixeldungeon.plants.Swiftthistle;
import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene;
import com.shatteredpixel.shatteredpixeldungeon.scenes.InterlevelScene;
import com.shatteredpixel.shatteredpixeldungeon.utils.BArray;
import com.shatteredpixel.shatteredpixeldungeon.utils.GLog;
import com.shatteredpixel.shatteredpixeldungeon.windows.WndOptions;
import com.watabou.noosa.Game;
import com.watabou.noosa.Image;
import com.watabou.noosa.audio.Sample;
import com.watabou.noosa.particles.Emitter;
import com.watabou.utils.Bundle;
import com.watabou.utils.PathFinder;
import com.watabou.utils.Random;
import java.util.ArrayList;
public class WarpBeacon extends ArmorAbility {
@Override
protected String targetingPrompt() {
if (Dungeon.hero.buff(WarpBeaconTracker.class) == null
&& Dungeon.hero.hasTalent(Talent.REMOTE_BEACON)){
return Messages.get(this, "prompt");
}
return super.targetingPrompt();
}
@Override
protected void activate(ClassArmor armor, Hero hero, Integer target) {
if (target == null){
return;
}
if (hero.buff(WarpBeaconTracker.class) != null){
final WarpBeaconTracker tracker = hero.buff(WarpBeaconTracker.class);
GameScene.show( new WndOptions(
new Image(hero.sprite),
Messages.titleCase(name()),
Messages.get(WarpBeacon.class, "window_desc", tracker.depth),
Messages.get(WarpBeacon.class, "window_tele"),
Messages.get(WarpBeacon.class, "window_clear"),
Messages.get(WarpBeacon.class, "window_cancel")){
@Override
protected void onSelect(int index) {
if (index == 0){
if (tracker.depth != Dungeon.depth && !hero.hasTalent(Talent.LONGRANGE_WARP)){
GLog.w( Messages.get(WarpBeacon.class, "depths") );
return;
}
float chargeNeeded = chargeUse(hero);
if (tracker.depth != Dungeon.depth){
chargeNeeded *= 1.833f - 0.333f*Dungeon.hero.pointsInTalent(Talent.LONGRANGE_WARP);
}
if (armor.charge < chargeNeeded){
GLog.w( Messages.get(ClassArmor.class, "low_charge") );
return;
}
if (tracker.depth == Dungeon.depth){
Char existing = Actor.findChar(tracker.pos);
if (existing != null){
if (hero.hasTalent(Talent.TELEFRAG)){
int heroHP = hero.HP + hero.shielding();
int heroDmg = Math.round(1.666f + 3.333f*hero.pointsInTalent(Talent.TELEFRAG));
hero.damage(Math.min(heroDmg, heroHP-1), WarpBeacon.this);
int damage = Random.NormalIntRange(10*hero.pointsInTalent(Talent.TELEFRAG), 15*hero.pointsInTalent(Talent.TELEFRAG));
existing.sprite.flash();
existing.sprite.bloodBurstA(existing.sprite.center(), damage);
existing.damage(damage, WarpBeacon.this);
Sample.INSTANCE.play(Assets.Sounds.HIT_CRUSH);
Sample.INSTANCE.play(Assets.Sounds.HIT_STRONG);
}
if (existing.isAlive()){
ArrayList<Integer> candidates = new ArrayList<>();
for (int n : PathFinder.NEIGHBOURS8) {
int cell = target + n;
if (!Dungeon.level.solid[cell] && Actor.findChar( cell ) == null
&& (!Char.hasProp(existing, Char.Property.LARGE) || Dungeon.level.openSpace[cell])) {
candidates.add( cell );
}
}
Random.shuffle(candidates);
if (!candidates.isEmpty()){
Actor.addDelayed( new Pushing( existing, existing.pos, candidates.get(0) ), -1 );
existing.pos = candidates.get(0);
Dungeon.level.occupyCell(existing);
hero.next();
}
}
}
ScrollOfTeleportation.appear(hero, tracker.pos);
Dungeon.observe();
} else {
if (hero.buff(LockedFloor.class) != null){
GLog.w( Messages.get(WarpBeacon.class, "locked_floor") );
return;
}
Buff buff = Dungeon.hero.buff(TimekeepersHourglass.timeFreeze.class);
if (buff != null) buff.detach();
buff = Dungeon.hero.buff(Swiftthistle.TimeBubble.class);
if (buff != null) buff.detach();
InterlevelScene.mode = InterlevelScene.Mode.RETURN;
InterlevelScene.returnDepth = tracker.depth;
InterlevelScene.returnPos = tracker.pos;
Game.switchScene( InterlevelScene.class );
}
} else if (index == 1){
hero.buff(WarpBeaconTracker.class).detach();
}
}
} );
} else {
if (!Dungeon.level.mapped[target] && !Dungeon.level.visited[target]){
return;
}
if (Dungeon.level.distance(hero.pos, target) > 3*hero.pointsInTalent(Talent.REMOTE_BEACON)){
GLog.w( Messages.get(WarpBeacon.class, "too_far") );
return;
}
PathFinder.buildDistanceMap(target, BArray.or(Dungeon.level.passable, Dungeon.level.avoid, null));
if (Dungeon.level.pit[target] ||
(Dungeon.level.solid[target] && !Dungeon.level.passable[target]) ||
PathFinder.distance[hero.pos] == Integer.MAX_VALUE){
GLog.w( Messages.get(WarpBeacon.class, "invalid_beacon") );
return;
}
WarpBeaconTracker tracker = new WarpBeaconTracker();
tracker.pos = target;
tracker.depth = Dungeon.depth;
tracker.attachTo(hero);
hero.sprite.operate(target);
Sample.INSTANCE.play(Assets.Sounds.TELEPORT);
hero.spendAndNext(Actor.TICK);
}
}
public static class WarpBeaconTracker extends Buff {
int pos;
int depth;
Emitter e;
@Override
public void fx(boolean on) {
if (on && depth == Dungeon.depth) {
e = CellEmitter.center(pos);
e.pour(MagicMissile.WardParticle.UP, 0.05f);
}
else if (e != null) e.on = false;
}
public static final String POS = "pos";
public static final String DEPTH = "depth";
@Override
public void storeInBundle(Bundle bundle) {
super.storeInBundle(bundle);
bundle.put(POS, pos);
bundle.put(DEPTH, depth);
}
@Override
public void restoreFromBundle(Bundle bundle) {
super.restoreFromBundle(bundle);
pos = bundle.getInt(POS);
depth = bundle.getInt(DEPTH);
}
}
@Override
public Talent[] talents() {
return new Talent[]{Talent.TELEFRAG, Talent.REMOTE_BEACON, Talent.LONGRANGE_WARP, Talent.HEROIC_ENERGY};
}
}

View File

@@ -134,6 +134,7 @@ public class DeathMark extends ArmorAbility {
if (!target.isAlive()){
target.sprite.flash();
target.sprite.bloodBurstA(target.sprite.center(), target.HT*2);
Sample.INSTANCE.play(Assets.Sounds.HIT_STAB);
Sample.INSTANCE.play(Assets.Sounds.HIT_STRONG);
target.die(this);
int shld = Math.round(initialHP * (0.1f*Dungeon.hero.pointsInTalent(Talent.DEATHLY_DURABILITY)));

View File

@@ -28,6 +28,7 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Hex;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.MagicalSleep;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Vulnerable;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Weakness;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.mage.WarpBeacon;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.DM100;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Eye;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Shaman;
@@ -78,6 +79,8 @@ public class AntiMagic extends Armor.Glyph {
RESISTS.add( WandOfPrismaticLight.class );
RESISTS.add( WandOfTransfusion.class );
RESISTS.add( WandOfWarding.Ward.class );
RESISTS.add( WarpBeacon.class );
RESISTS.add( DM100.LightningBolt.class );
RESISTS.add( Shaman.EarthenBolt.class );