diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/wands/WandOfFireblast.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/wands/WandOfFireblast.java index 685e4a14a..bca83cafa 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/wands/WandOfFireblast.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/wands/WandOfFireblast.java @@ -35,6 +35,7 @@ import com.shatteredpixel.shatteredpixeldungeon.effects.MagicMissile; import com.shatteredpixel.shatteredpixeldungeon.items.weapon.enchantments.Blazing; import com.shatteredpixel.shatteredpixeldungeon.items.weapon.melee.MagesStaff; import com.shatteredpixel.shatteredpixeldungeon.mechanics.Ballistica; +import com.shatteredpixel.shatteredpixeldungeon.mechanics.ConeAOE; import com.shatteredpixel.shatteredpixeldungeon.messages.Messages; import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene; import com.shatteredpixel.shatteredpixeldungeon.sprites.ItemSpriteSheet; @@ -63,35 +64,27 @@ public class WandOfFireblast extends DamageWand { return (6+2*lvl) * chargesPerCast(); } - //the actual affected cells - private HashSet affectedCells; - //the cells to trace fire shots to, for visual effects. - private HashSet visualCells; - private int direction = 0; - + ConeAOE cone; + @Override protected void onZap( Ballistica bolt ) { - + ArrayList affectedChars = new ArrayList<>(); - for( int cell : affectedCells){ - + for( int cell : cone.cells ){ + //ignore caster cell if (cell == bolt.sourcePos){ continue; } - - //only ignite cells directly near caster if they are flammable - if (!Dungeon.level.adjacent(bolt.sourcePos, cell) - || Dungeon.level.flamable[cell]){ - GameScene.add( Blob.seed( cell, 1+chargesPerCast(), Fire.class ) ); - } - + + GameScene.add( Blob.seed( cell, 1+chargesPerCast(), Fire.class ) ); + Char ch = Actor.findChar( cell ); if (ch != null) { affectedChars.add(ch); } } - + for ( Char ch : affectedChars ){ processSoulMark(ch, chargesPerCast()); ch.damage(damageRoll(), this); @@ -107,30 +100,6 @@ public class WandOfFireblast extends DamageWand { } } - //burn... BURNNNNN!..... - private void spreadFlames(int cell, float strength){ - if (strength >= 0 && (Dungeon.level.passable[cell] || Dungeon.level.flamable[cell])){ - affectedCells.add(cell); - if (strength >= 1.5f) { - visualCells.remove(cell); - spreadFlames(cell + PathFinder.CIRCLE8[left(direction)], strength - 1.5f); - spreadFlames(cell + PathFinder.CIRCLE8[direction], strength - 1.5f); - spreadFlames(cell + PathFinder.CIRCLE8[right(direction)], strength - 1.5f); - } else { - visualCells.add(cell); - } - } else if (!Dungeon.level.passable[cell]) - visualCells.add(cell); - } - - private int left(int direction){ - return direction == 0 ? 7 : direction-1; - } - - private int right(int direction){ - return direction == 7 ? 0 : direction+1; - } - @Override public void onHit(MagesStaff staff, Char attacker, Char defender, int damage) { //acts like blazing enchantment @@ -140,45 +109,27 @@ public class WandOfFireblast extends DamageWand { @Override protected void fx( Ballistica bolt, Callback callback ) { //need to perform flame spread logic here so we can determine what cells to put flames in. - affectedCells = new HashSet<>(); - visualCells = new HashSet<>(); // 4/6/8 distance int maxDist = 2 + 2*chargesPerCast(); int dist = Math.min(bolt.dist, maxDist); - for (int i = 0; i < PathFinder.CIRCLE8.length; i++){ - if (bolt.sourcePos+PathFinder.CIRCLE8[i] == bolt.path.get(1)){ - direction = i; - break; - } - } + cone = new ConeAOE( bolt.sourcePos, bolt.path.get(dist), + maxDist, + 30 + 20*chargesPerCast(), + collisionProperties | Ballistica.STOP_TARGET); - float strength = maxDist; - for (int c : bolt.subPath(1, dist)) { - strength--; //as we start at dist 1, not 0. - affectedCells.add(c); - if (strength > 1) { - spreadFlames(c + PathFinder.CIRCLE8[left(direction)], strength - 1); - spreadFlames(c + PathFinder.CIRCLE8[direction], strength - 1); - spreadFlames(c + PathFinder.CIRCLE8[right(direction)], strength - 1); - } else { - visualCells.add(c); - } - } - - //going to call this one manually - visualCells.remove(bolt.path.get(dist)); - - for (int cell : visualCells){ - //this way we only get the cells at the tip, much better performance. + //cast to cells at the tip, rather than all cells, better performance. + for (Ballistica ray : cone.rays){ ((MagicMissile)curUser.sprite.parent.recycle( MagicMissile.class )).reset( MagicMissile.FIRE_CONE, curUser.sprite, - cell, + ray.path.get(ray.dist), null ); } + + //final zap at half distance, for timing of the actual wand effect MagicMissile.boltFromChar( curUser.sprite.parent, MagicMissile.FIRE_CONE, curUser.sprite, diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/mechanics/ConeAOE.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/mechanics/ConeAOE.java new file mode 100644 index 000000000..30d28cf1f --- /dev/null +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/mechanics/ConeAOE.java @@ -0,0 +1,104 @@ +/* + * Pixel Dungeon + * Copyright (C) 2012-2015 Oleg Dolya + * + * Shattered Pixel Dungeon + * Copyright (C) 2014-2020 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 + */ + +package com.shatteredpixel.shatteredpixeldungeon.mechanics; + +import com.shatteredpixel.shatteredpixeldungeon.Dungeon; +import com.watabou.utils.GameMath; +import com.watabou.utils.Point; +import com.watabou.utils.PointF; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashSet; + +//a cone made of up several ballisticas scanning in an arc +public class ConeAOE { + + public ArrayList rays = new ArrayList<>(); + public HashSet cells = new HashSet<>(); + + public ConeAOE( int from, int to, float degrees ){ + this(from, to, Float.POSITIVE_INFINITY, degrees, Ballistica.STOP_TARGET); + } + + public ConeAOE( int from, int to, float maxDist, float degrees, int ballisticaParams ){ + + //we want to use true coordinates for our trig functions, not game cells + // so get the center of from and to as points + PointF fromP = new PointF(Dungeon.level.cellToPoint(from)); + fromP.x += 0.5f; + fromP.y += 0.5f; + PointF toP = new PointF(Dungeon.level.cellToPoint(to)); + toP.x += 0.5f; + toP.y += 0.5f; + + //clamp distance of cone to maxDist (in true distance, not game distance) + if (PointF.distance(fromP, toP) > maxDist){ + toP = PointF.inter(fromP, toP, maxDist/PointF.distance(fromP, toP) ); + } + + //now we can get the circle's radius. We bump it by 0.5 as we want the cone to reach + // The edge of the target cell, not the center. + float circleRadius = PointF.distance(fromP, toP); + circleRadius += 0.5f; + + //Now we find every unique cell along the outer arc of our cone. + PointF scan = new PointF(); + Point scanInt = new Point(); + float initalAngle = PointF.angle(fromP, toP)/PointF.G2R; + //want to preserve order so that our collection of rays is going clockwise + LinkedHashSet targetCells = new LinkedHashSet<>(); + + //cast a ray every 0.5 degrees in a clockwise arc, to find cells along the cone's outer arc + for (float a = initalAngle+degrees/2f; a >= initalAngle-degrees/2f; a-=0.5f){ + scan.polar(a * PointF.G2R, circleRadius); + scan.offset(fromP); + scan.x += (fromP.x > scan.x ? +0.5f : -0.5f); + scan.y += (fromP.y > scan.y ? +0.5f : -0.5f); + scanInt.set( + (int)GameMath.gate(0, (int)Math.floor(scan.x), Dungeon.level.width()-1), + (int)GameMath.gate(0, (int)Math.floor(scan.y), Dungeon.level.height()-1)); + targetCells.add(Dungeon.level.pointToCell(scanInt)); + //if the cone is large enough, also cast rays to cells just inside of the outer arc + // this helps fill in any holes when casting rays + if (circleRadius >= 7) { + scan.polar(a * PointF.G2R, circleRadius - 1); + scan.offset(fromP); + scan.x += (fromP.x > scan.x ? +0.5f : -0.5f); + scan.y += (fromP.y > scan.y ? +0.5f : -0.5f); + scanInt.set( + (int)GameMath.gate(0, (int)Math.floor(scan.x), Dungeon.level.width()-1), + (int)GameMath.gate(0, (int)Math.floor(scan.y), Dungeon.level.height()-1)); + targetCells.add(Dungeon.level.pointToCell(scanInt)); + } + } + + //cast a ray to each found cell, these make up the cone + for( int c : targetCells ){ + Ballistica ray = new Ballistica(from, c, ballisticaParams); + cells.addAll(ray.subPath(1, ray.dist)); + rays.add(ray); + } + + } + +}