v3.0.0: implemented the wall of light talent/spell

This commit is contained in:
Evan Debenham
2025-01-21 13:53:05 -05:00
parent 49f08e1fe4
commit cabd114bb5
3 changed files with 261 additions and 1 deletions

View File

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

View File

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

View File

@@ -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() {