v3.0.0: implemented the wall of light talent/spell
This commit is contained in:
@@ -695,7 +695,9 @@ actors.hero.spells.sunray.desc=The Cleric fires a ray of blinding light at a tar
|
|||||||
|
|
||||||
actors.hero.spells.walloflight.name=wall of light
|
actors.hero.spells.walloflight.name=wall of light
|
||||||
actors.hero.spells.walloflight.short_desc=Creates a wall that blocks enemies.
|
actors.hero.spells.walloflight.short_desc=Creates a wall that blocks enemies.
|
||||||
actors.hero.spells.walloflight.desc=The Paladin creates a wall of solid light directly in front of themselves that's 1 tile thick, %1$d tiles wide, and lasts for 20 turns.\n\nThis wall blocks movement and ranged attacks, but can be seen through. Enemies that are caught in the wall when its created will be pushed back if possible, otherwise they will be caught inside it and be able to exit through either side.\n\nThe wall can be cast in any of the four cardinal or four diagonal directions. Only one wall can exist at a time.
|
actors.hero.spells.walloflight.desc=The Paladin creates a wall made out of panels of solid light directly in front of themselves which is 1 tile thick, %1$d tiles wide, and lasts for 20 turns.\n\nThis wall acts just like a regular once, except it can be seen through. Enemies that are caught in the wall when its created will be momentarily stunned and pushed back if possible. Anything stuck in the wall will be able to move out of it.\n\nThe wall can be cast in any of the four cardinal or four diagonal directions. If a wall is already active, the spell can be re-used for free to instantly clear the wall.
|
||||||
|
actors.hero.spells.walloflight.early_end=You dispel the wall of light.
|
||||||
|
actors.hero.spells.walloflight$lightwall.desc=Shimmering panels are light are blocking passage here.
|
||||||
|
|
||||||
##main hero
|
##main hero
|
||||||
actors.hero.hero.name=you
|
actors.hero.hero.name=you
|
||||||
|
|||||||
@@ -21,12 +21,28 @@
|
|||||||
|
|
||||||
package com.shatteredpixel.shatteredpixeldungeon.actors.hero.spells;
|
package com.shatteredpixel.shatteredpixeldungeon.actors.hero.spells;
|
||||||
|
|
||||||
|
import com.shatteredpixel.shatteredpixeldungeon.Assets;
|
||||||
import com.shatteredpixel.shatteredpixeldungeon.Dungeon;
|
import com.shatteredpixel.shatteredpixeldungeon.Dungeon;
|
||||||
|
import com.shatteredpixel.shatteredpixeldungeon.actors.Actor;
|
||||||
|
import com.shatteredpixel.shatteredpixeldungeon.actors.Char;
|
||||||
|
import com.shatteredpixel.shatteredpixeldungeon.actors.blobs.Blob;
|
||||||
|
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff;
|
||||||
|
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Paralysis;
|
||||||
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero;
|
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero;
|
||||||
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Talent;
|
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Talent;
|
||||||
|
import com.shatteredpixel.shatteredpixeldungeon.effects.BlobEmitter;
|
||||||
|
import com.shatteredpixel.shatteredpixeldungeon.effects.MagicMissile;
|
||||||
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.HolyTome;
|
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.HolyTome;
|
||||||
|
import com.shatteredpixel.shatteredpixeldungeon.items.wands.WandOfBlastWave;
|
||||||
|
import com.shatteredpixel.shatteredpixeldungeon.levels.Level;
|
||||||
|
import com.shatteredpixel.shatteredpixeldungeon.levels.Terrain;
|
||||||
|
import com.shatteredpixel.shatteredpixeldungeon.mechanics.Ballistica;
|
||||||
import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
|
import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
|
||||||
|
import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene;
|
||||||
import com.shatteredpixel.shatteredpixeldungeon.ui.HeroIcon;
|
import com.shatteredpixel.shatteredpixeldungeon.ui.HeroIcon;
|
||||||
|
import com.shatteredpixel.shatteredpixeldungeon.utils.GLog;
|
||||||
|
import com.watabou.noosa.audio.Sample;
|
||||||
|
import com.watabou.utils.PathFinder;
|
||||||
|
|
||||||
public class WallOfLight extends TargetedClericSpell {
|
public class WallOfLight extends TargetedClericSpell {
|
||||||
|
|
||||||
@@ -49,6 +65,10 @@ public class WallOfLight extends TargetedClericSpell {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float chargeUse(Hero hero) {
|
public float chargeUse(Hero hero) {
|
||||||
|
if (Dungeon.level.blobs.get(LightWall.class) != null
|
||||||
|
&& Dungeon.level.blobs.get(LightWall.class).volume > 0){
|
||||||
|
return 0f;
|
||||||
|
}
|
||||||
return 3f;
|
return 3f;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,8 +77,225 @@ public class WallOfLight extends TargetedClericSpell {
|
|||||||
return super.canCast(hero) && hero.hasTalent(Talent.WALL_OF_LIGHT);
|
return super.canCast(hero) && hero.hasTalent(Talent.WALL_OF_LIGHT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCast(HolyTome tome, Hero hero) {
|
||||||
|
if (Dungeon.level.blobs.get(LightWall.class) != null
|
||||||
|
&& Dungeon.level.blobs.get(LightWall.class).volume > 0){
|
||||||
|
Dungeon.level.blobs.get(LightWall.class).fullyClear();
|
||||||
|
GLog.i(Messages.get(this, "early_end"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
super.onCast(tome, hero);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onTargetSelected(HolyTome tome, Hero hero, Integer target) {
|
protected void onTargetSelected(HolyTome tome, Hero hero, Integer target) {
|
||||||
|
if (target == null){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target == hero.pos){
|
||||||
|
GLog.w(Messages.get(this, "invalid_target"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int closest = hero.pos;
|
||||||
|
int closestIdx = -1;
|
||||||
|
|
||||||
|
for (int i = 0; i < PathFinder.CIRCLE8.length; i++){
|
||||||
|
int ofs = PathFinder.CIRCLE8[i];
|
||||||
|
if (Dungeon.level.trueDistance(target, hero.pos+ofs) < Dungeon.level.trueDistance(target, closest)){
|
||||||
|
closest = hero.pos+ofs;
|
||||||
|
closestIdx = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int leftDirX = 0;
|
||||||
|
int leftDirY = 0;
|
||||||
|
|
||||||
|
int rightDirX = 0;
|
||||||
|
int rightDirY = 0;
|
||||||
|
|
||||||
|
int steps = Dungeon.hero.pointsInTalent(Talent.WALL_OF_LIGHT);
|
||||||
|
|
||||||
|
switch (closestIdx){
|
||||||
|
case 0: //top left
|
||||||
|
leftDirX = -1;
|
||||||
|
leftDirY = 1;
|
||||||
|
rightDirX = 1;
|
||||||
|
rightDirY = -1;
|
||||||
|
break;
|
||||||
|
case 1: //top
|
||||||
|
leftDirX = -1;
|
||||||
|
rightDirX = 1;
|
||||||
|
leftDirY = rightDirY = 0;
|
||||||
|
break;
|
||||||
|
case 2: //top right (left and right DIR are purposefully inverted)
|
||||||
|
leftDirX = 1;
|
||||||
|
leftDirY = 1;
|
||||||
|
rightDirX = -1;
|
||||||
|
rightDirY = -1;
|
||||||
|
break;
|
||||||
|
case 3: //right
|
||||||
|
leftDirY = -1;
|
||||||
|
rightDirY = 1;
|
||||||
|
leftDirX = rightDirX = 0;
|
||||||
|
break;
|
||||||
|
case 4: //bottom right (left and right DIR are purposefully inverted)
|
||||||
|
leftDirX = 1;
|
||||||
|
leftDirY = -1;
|
||||||
|
rightDirX = -1;
|
||||||
|
rightDirY = 1;
|
||||||
|
break;
|
||||||
|
case 5: //bottom
|
||||||
|
leftDirX = 1;
|
||||||
|
rightDirX = -1;
|
||||||
|
leftDirY = rightDirY = 0;
|
||||||
|
break;
|
||||||
|
case 6: //bottom left
|
||||||
|
leftDirX = -1;
|
||||||
|
leftDirY = -1;
|
||||||
|
rightDirX = 1;
|
||||||
|
rightDirY = 1;
|
||||||
|
break;
|
||||||
|
case 7: //left
|
||||||
|
leftDirY = -1;
|
||||||
|
rightDirY = 1;
|
||||||
|
leftDirX = rightDirX = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Dungeon.level.blobs.get(LightWall.class) != null){
|
||||||
|
Dungeon.level.blobs.get(LightWall.class).fullyClear();
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean placedWall = false;
|
||||||
|
|
||||||
|
int knockBackDir = PathFinder.CIRCLE8[closestIdx];
|
||||||
|
|
||||||
|
//if all 3 tiles infront of Paladin are blocked, assume cast was in error and cancel
|
||||||
|
if (Dungeon.level.solid[closest]
|
||||||
|
&& Dungeon.level.solid[hero.pos + PathFinder.CIRCLE8[(closestIdx+1)%8]]
|
||||||
|
&& Dungeon.level.solid[hero.pos + PathFinder.CIRCLE8[(closestIdx+7)%8]]){
|
||||||
|
GLog.w(Messages.get(this, "invalid_target"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//process early so that cost is calculated before walls are added
|
||||||
|
onSpellCast(tome, hero);
|
||||||
|
|
||||||
|
placeWall(closest, knockBackDir);
|
||||||
|
|
||||||
|
int leftPos = closest;
|
||||||
|
int rightPos = closest;
|
||||||
|
|
||||||
|
//iterate to the left and right, placing walls as we go
|
||||||
|
for (int i = 0; i < steps; i++) {
|
||||||
|
if (leftDirY != 0) {
|
||||||
|
leftPos += leftDirY * Dungeon.level.width();
|
||||||
|
placeWall(leftPos, knockBackDir);
|
||||||
|
}
|
||||||
|
if (leftDirX != 0) {
|
||||||
|
leftPos += leftDirX;
|
||||||
|
placeWall(leftPos, knockBackDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rightDirX != 0) {
|
||||||
|
rightPos += rightDirX;
|
||||||
|
placeWall(rightPos, knockBackDir);
|
||||||
|
}
|
||||||
|
if (rightDirY != 0) {
|
||||||
|
rightPos += rightDirY * Dungeon.level.width();
|
||||||
|
placeWall(rightPos, knockBackDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Sample.INSTANCE.play(Assets.Sounds.CHARGEUP);
|
||||||
|
|
||||||
|
hero.sprite.zap(closest);
|
||||||
|
Dungeon.hero.spendAndNext(1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void placeWall( int pos, int knockbackDIR){
|
||||||
|
if (!Dungeon.level.solid[pos]) {
|
||||||
|
GameScene.add(Blob.seed(pos, 20, LightWall.class));
|
||||||
|
|
||||||
|
Char ch = Actor.findChar(pos);
|
||||||
|
if (ch != null && ch.alignment == Char.Alignment.ENEMY){
|
||||||
|
WandOfBlastWave.throwChar(ch, new Ballistica(pos, pos+knockbackDIR, Ballistica.PROJECTILE), 1, false, false, WallOfLight.INSTANCE);
|
||||||
|
Buff.affect(ch, Paralysis.class, ch.cooldown());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class LightWall extends Blob {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void evolve() {
|
||||||
|
|
||||||
|
int cell;
|
||||||
|
|
||||||
|
Level l = Dungeon.level;
|
||||||
|
for (int i = area.left; i < area.right; i++){
|
||||||
|
for (int j = area.top; j < area.bottom; j++){
|
||||||
|
cell = i + j*l.width();
|
||||||
|
off[cell] = cur[cell] > 0 ? cur[cell] - 1 : 0;
|
||||||
|
|
||||||
|
volume += off[cell];
|
||||||
|
|
||||||
|
l.solid[cell] = off[cell] > 0 || (Terrain.flags[l.map[cell]] & Terrain.SOLID) != 0;
|
||||||
|
l.passable[cell] = off[cell] == 0 && (Terrain.flags[l.map[cell]] & Terrain.PASSABLE) != 0;
|
||||||
|
l.avoid[cell] = off[cell] == 0 && (Terrain.flags[l.map[cell]] & Terrain.AVOID) != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void seed(Level level, int cell, int amount) {
|
||||||
|
super.seed(level, cell, amount);
|
||||||
|
level.solid[cell] = cur[cell] > 0 || (Terrain.flags[level.map[cell]] & Terrain.SOLID) != 0;
|
||||||
|
level.passable[cell] = cur[cell] == 0 && (Terrain.flags[level.map[cell]] & Terrain.PASSABLE) != 0;
|
||||||
|
level.avoid[cell] = cur[cell] == 0 && (Terrain.flags[level.map[cell]] & Terrain.AVOID) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear(int cell) {
|
||||||
|
super.clear(cell);
|
||||||
|
if (cur == null) return;
|
||||||
|
Level l = Dungeon.level;
|
||||||
|
l.solid[cell] = cur[cell] > 0 || (Terrain.flags[l.map[cell]] & Terrain.SOLID) != 0;
|
||||||
|
l.passable[cell] = cur[cell] == 0 && (Terrain.flags[l.map[cell]] & Terrain.PASSABLE) != 0;
|
||||||
|
l.avoid[cell] = cur[cell] == 0 && (Terrain.flags[l.map[cell]] & Terrain.AVOID) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void fullyClear() {
|
||||||
|
super.fullyClear();
|
||||||
|
Dungeon.level.buildFlagMaps();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBuildFlagMaps(Level l) {
|
||||||
|
if (volume > 0){
|
||||||
|
for (int i=0; i < l.length(); i++) {
|
||||||
|
l.solid[i] = l.solid[i] || cur[i] > 0;
|
||||||
|
l.passable[i] = l.passable[i] && cur[i] == 0;
|
||||||
|
l.avoid[i] = l.avoid[i] && cur[i] == 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void use(BlobEmitter emitter) {
|
||||||
|
super.use( emitter );
|
||||||
|
emitter.pour( MagicMissile.WhiteParticle.WALL, 0.02f );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String tileDesc() {
|
||||||
|
return Messages.get(this, "desc");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -509,6 +509,17 @@ public class MagicMissile extends Emitter {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public static final Emitter.Factory WALL = new Factory() {
|
||||||
|
@Override
|
||||||
|
public void emit( Emitter emitter, int index, float x, float y ) {
|
||||||
|
((WhiteParticle)emitter.recycle( WhiteParticle.class )).resetWall( x, y );
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public boolean lightMode() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public WhiteParticle() {
|
public WhiteParticle() {
|
||||||
super();
|
super();
|
||||||
@@ -532,6 +543,16 @@ public class MagicMissile extends Emitter {
|
|||||||
reset(x, y);
|
reset(x, y);
|
||||||
hardlight(r, g, b);
|
hardlight(r, g, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void resetWall( float x, float y){
|
||||||
|
reset(x, y);
|
||||||
|
|
||||||
|
left = lifespan = 2f;
|
||||||
|
|
||||||
|
this.x = Math.round(x/4)*4;
|
||||||
|
this.y = Math.round(y/4)*4 - 6;
|
||||||
|
this.x += Math.round(this.y % 16)/4f - 2;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void update() {
|
public void update() {
|
||||||
|
|||||||
Reference in New Issue
Block a user