v0.8.1: wrote new logic for conical AOEs, and moved fireblast over to it

This commit is contained in:
Evan Debenham 2020-06-05 14:43:52 -04:00
parent 985b20ebff
commit 3fa4f6256f
2 changed files with 123 additions and 68 deletions

View File

@ -35,6 +35,7 @@ import com.shatteredpixel.shatteredpixeldungeon.effects.MagicMissile;
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.enchantments.Blazing; import com.shatteredpixel.shatteredpixeldungeon.items.weapon.enchantments.Blazing;
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.melee.MagesStaff; import com.shatteredpixel.shatteredpixeldungeon.items.weapon.melee.MagesStaff;
import com.shatteredpixel.shatteredpixeldungeon.mechanics.Ballistica; import com.shatteredpixel.shatteredpixeldungeon.mechanics.Ballistica;
import com.shatteredpixel.shatteredpixeldungeon.mechanics.ConeAOE;
import com.shatteredpixel.shatteredpixeldungeon.messages.Messages; import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene; import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene;
import com.shatteredpixel.shatteredpixeldungeon.sprites.ItemSpriteSheet; import com.shatteredpixel.shatteredpixeldungeon.sprites.ItemSpriteSheet;
@ -63,28 +64,20 @@ public class WandOfFireblast extends DamageWand {
return (6+2*lvl) * chargesPerCast(); return (6+2*lvl) * chargesPerCast();
} }
//the actual affected cells ConeAOE cone;
private HashSet<Integer> affectedCells;
//the cells to trace fire shots to, for visual effects.
private HashSet<Integer> visualCells;
private int direction = 0;
@Override @Override
protected void onZap( Ballistica bolt ) { protected void onZap( Ballistica bolt ) {
ArrayList<Char> affectedChars = new ArrayList<>(); ArrayList<Char> affectedChars = new ArrayList<>();
for( int cell : affectedCells){ for( int cell : cone.cells ){
//ignore caster cell //ignore caster cell
if (cell == bolt.sourcePos){ if (cell == bolt.sourcePos){
continue; 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 ); Char ch = Actor.findChar( cell );
if (ch != null) { if (ch != null) {
@ -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 @Override
public void onHit(MagesStaff staff, Char attacker, Char defender, int damage) { public void onHit(MagesStaff staff, Char attacker, Char defender, int damage) {
//acts like blazing enchantment //acts like blazing enchantment
@ -140,45 +109,27 @@ public class WandOfFireblast extends DamageWand {
@Override @Override
protected void fx( Ballistica bolt, Callback callback ) { protected void fx( Ballistica bolt, Callback callback ) {
//need to perform flame spread logic here so we can determine what cells to put flames in. //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 // 4/6/8 distance
int maxDist = 2 + 2*chargesPerCast(); int maxDist = 2 + 2*chargesPerCast();
int dist = Math.min(bolt.dist, maxDist); int dist = Math.min(bolt.dist, maxDist);
for (int i = 0; i < PathFinder.CIRCLE8.length; i++){ cone = new ConeAOE( bolt.sourcePos, bolt.path.get(dist),
if (bolt.sourcePos+PathFinder.CIRCLE8[i] == bolt.path.get(1)){ maxDist,
direction = i; 30 + 20*chargesPerCast(),
break; collisionProperties | Ballistica.STOP_TARGET);
}
}
float strength = maxDist; //cast to cells at the tip, rather than all cells, better performance.
for (int c : bolt.subPath(1, dist)) { for (Ballistica ray : cone.rays){
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.
((MagicMissile)curUser.sprite.parent.recycle( MagicMissile.class )).reset( ((MagicMissile)curUser.sprite.parent.recycle( MagicMissile.class )).reset(
MagicMissile.FIRE_CONE, MagicMissile.FIRE_CONE,
curUser.sprite, curUser.sprite,
cell, ray.path.get(ray.dist),
null null
); );
} }
//final zap at half distance, for timing of the actual wand effect
MagicMissile.boltFromChar( curUser.sprite.parent, MagicMissile.boltFromChar( curUser.sprite.parent,
MagicMissile.FIRE_CONE, MagicMissile.FIRE_CONE,
curUser.sprite, curUser.sprite,

View File

@ -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 <http://www.gnu.org/licenses/>
*/
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<Ballistica> rays = new ArrayList<>();
public HashSet<Integer> 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<Integer> 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);
}
}
}