FireBlast

This commit is contained in:
MistPhizzle 2014-06-26 16:51:46 -04:00
parent 62e54864ae
commit 256ad9fd59
10 changed files with 676 additions and 1 deletions

View file

@ -25,9 +25,11 @@ import com.projectkorra.ProjectKorra.earthbending.CompactColumn;
import com.projectkorra.ProjectKorra.earthbending.EarthColumn;
import com.projectkorra.ProjectKorra.earthbending.EarthPassive;
import com.projectkorra.ProjectKorra.firebending.Cook;
import com.projectkorra.ProjectKorra.firebending.FireBlast;
import com.projectkorra.ProjectKorra.firebending.FireJet;
import com.projectkorra.ProjectKorra.firebending.FirePassive;
import com.projectkorra.ProjectKorra.firebending.FireStream;
import com.projectkorra.ProjectKorra.firebending.Fireball;
import com.projectkorra.ProjectKorra.firebending.Illumination;
import com.projectkorra.ProjectKorra.waterbending.Bloodbending;
import com.projectkorra.ProjectKorra.waterbending.FreezeMelt;
@ -93,6 +95,8 @@ public class BendingManager implements Runnable {
Illumination.manage(Bukkit.getServer());
Torrent.progressAll();
TorrentBurst.progressAll();
FireBlast.progressAll();
Fireball.progressAll();
for (int ID: Tornado.instances.keySet()) {
Tornado.progress(ID);
}

View file

@ -142,7 +142,7 @@ public class ConfigManager {
config.addDefault("Abilities.Water.IceSpike.Range", 20);
config.addDefault("Abilities.Water.IceSpike.ThrowingMult", 0.7);
config.addDefault("Abilities.Water.IceSpike.Height", 6);
config.addDefault("Abilities.Water.OctopusForm.Enabled", true);
config.addDefault("Abilities.Water.OctopusForm.Description", "This ability allows the waterbender to manipulate a large quantity of water into a form resembling that of an octopus. "
+ "To use, click to select a water source. Then, hold sneak to channel this ability. "
@ -228,6 +228,23 @@ public class ConfigManager {
config.addDefault("Abilities.Earth.RaiseEarth.Wall.Height", 6);
config.addDefault("Abilities.Earth.RaiseEarth.Wall.Width", 6);
config.addDefault("Abilities.Fire.FireBlast.Enabled", true);
config.addDefault("Abilities.Fire.FireBlast.Description","FireBlast is the most fundamental bending technique of a firebender. "
+ "To use, simply left-click in a direction. A blast of fire will be created at your fingertips. "
+ "If this blast contacts an enemy, it will dissipate and engulf them in flames, "
+ "doing additional damage and knocking them back slightly. "
+ "If the blast hits terrain, it will ignite the nearby area. "
+ "Additionally, if you hold sneak, you will charge up the fireblast. "
+ "If you release it when it's charged, it will instead launch a powerful "
+ "fireball that explodes on contact.");
config.addDefault("Abilities.Fire.FireBlast.Speed", 15);
config.addDefault("Abilities.Fire.FireBlast.Range", 15);
config.addDefault("Abilities.Fire.FireBlast.Radius", 2);
config.addDefault("Abilities.Fire.FireBlast.Push", 0.3);
config.addDefault("Abilities.Fire.FireBlast.Damage", 2);
config.addDefault("Abilities.Fire.FireBlast.Cooldown", 1500);
config.addDefault("Abilities.fire.FireBlast.Dissipate", false);
config.addDefault("Abilities.Fire.FireJet.Enabled", true);
config.addDefault("Abilities.Fire.FireJet.Description", "This ability is used for a limited burst of flight for firebenders. Clicking with this "
+ "ability selected will launch you in the direction you're looking, granting you "

View file

@ -33,10 +33,12 @@ import org.bukkit.util.Vector;
import com.projectkorra.ProjectKorra.Ability.AbilityModule;
import com.projectkorra.ProjectKorra.Ability.AbilityModuleManager;
import com.projectkorra.ProjectKorra.Ability.AvatarState;
import com.projectkorra.ProjectKorra.airbending.AirSpout;
import com.projectkorra.ProjectKorra.earthbending.EarthColumn;
import com.projectkorra.ProjectKorra.earthbending.EarthPassive;
import com.projectkorra.ProjectKorra.waterbending.FreezeMelt;
import com.projectkorra.ProjectKorra.waterbending.WaterManipulation;
import com.projectkorra.ProjectKorra.waterbending.WaterSpout;
public class Methods {
@ -460,6 +462,16 @@ public class Methods {
return false;
}
public static void removeSpouts(Location location, double radius,
Player sourceplayer) {
WaterSpout.removeSpouts(location, radius, sourceplayer);
AirSpout.removeSpouts(location, radius, sourceplayer);
}
public static void removeSpouts(Location location, Player sourceplayer) {
removeSpouts(location, 1.5, sourceplayer);
}
public static double firebendingDayAugment(double value, World world) {
if (isDay(world)) {
return plugin.getConfig().getDouble("Properties.Fire.DayFactor") * value;

View file

@ -60,8 +60,10 @@ import com.projectkorra.ProjectKorra.earthbending.EarthWall;
import com.projectkorra.ProjectKorra.firebending.Cook;
import com.projectkorra.ProjectKorra.firebending.Enflamed;
import com.projectkorra.ProjectKorra.firebending.Extinguish;
import com.projectkorra.ProjectKorra.firebending.FireBlast;
import com.projectkorra.ProjectKorra.firebending.FireJet;
import com.projectkorra.ProjectKorra.firebending.FireStream;
import com.projectkorra.ProjectKorra.firebending.Fireball;
import com.projectkorra.ProjectKorra.firebending.Illumination;
import com.projectkorra.ProjectKorra.waterbending.Bloodbending;
import com.projectkorra.ProjectKorra.waterbending.FreezeMelt;
@ -218,6 +220,9 @@ public class PKListener implements Listener {
if (Methods.isWeapon(player.getItemInHand().getType()) && !plugin.getConfig().getBoolean("Properties.Fire.CanBendWithWeapons")) {
return;
}
if (abil.equalsIgnoreCase("FireBlast")) {
new Fireball(player);
}
if (abil.equalsIgnoreCase("HeatControl")) {
new Cook(player);
}
@ -413,6 +418,9 @@ public class PKListener implements Listener {
return;
}
if (abil.equalsIgnoreCase("FireBlast")) {
new FireBlast(player);
}
if (abil.equalsIgnoreCase("FireJet")) {
new FireJet(player);
}

View file

@ -0,0 +1,324 @@
package com.projectkorra.ProjectKorra.firebending;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.bukkit.Effect;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.util.Vector;
import com.projectkorra.ProjectKorra.Methods;
import com.projectkorra.ProjectKorra.ProjectKorra;
import com.projectkorra.ProjectKorra.Ability.AvatarState;
import com.projectkorra.ProjectKorra.waterbending.Plantbending;
import com.projectkorra.ProjectKorra.waterbending.WaterManipulation;
public class FireBlast {
public static ConcurrentHashMap<Integer, FireBlast> instances = new ConcurrentHashMap<Integer, FireBlast>();
// private static ConcurrentHashMap<Player, Long> timers = new
// ConcurrentHashMap<Player, Long>();
// static final long soonesttime = ConfigManager.fireBlastCooldown;
private static int ID = Integer.MIN_VALUE;
static final int maxticks = 10000;
private static double speed = ProjectKorra.plugin.getConfig().getDouble("Abilities.Fire.FireBlast.Speed");
public static double affectingradius = 2;
private static double pushfactor = ProjectKorra.plugin.getConfig().getDouble("Abilities.Fire.FireBlast.Push");
private static boolean canPowerFurnace = true;
static boolean dissipate = ProjectKorra.plugin.getConfig().getBoolean("Abilities.Fire.FireBlast.Dissipate");
// public static long interval = 2000;
public static byte full = 0x0;
private Location location;
private List<Block> safe = new ArrayList<Block>();
private Location origin;
private Vector direction;
private Player player;
private int id;
private double speedfactor;
private int ticks = 0;
private int damage = ProjectKorra.plugin.getConfig().getInt("Abilities.Fire.FireBlast.Damage");
double range = ProjectKorra.plugin.getConfig().getDouble("Abilities.Fire.FireBlast.Range");
long cooldown = ProjectKorra.plugin.getConfig().getLong("Abilities.Fire.FireBlast.Cooldown");
public static Map<String, Long> cooldowns = new HashMap<String, Long>();
// private ArrayList<Block> affectedlevers = new ArrayList<Block>();
// private long time;
public FireBlast(Player player) {
// if (timers.containsKey(player)) {
// if (System.currentTimeMillis() < timers.get(player) + soonesttime) {
// return;
// }
// }
if (cooldowns.containsKey(player.getName())) {
if (cooldowns.get(player.getName()) + cooldown >= System.currentTimeMillis()) {
return;
} else {
cooldowns.remove(player.getName());
}
}
if (player.getEyeLocation().getBlock().isLiquid()
|| Fireball.isCharging(player)) {
return;
}
range = Methods.firebendingDayAugment(range, player.getWorld());
// timers.put(player, System.currentTimeMillis());
this.player = player;
location = player.getEyeLocation();
origin = player.getEyeLocation();
direction = player.getEyeLocation().getDirection().normalize();
location = location.add(direction.clone());
id = ID;
instances.put(id, this);
cooldowns.put(player.getName(), System.currentTimeMillis());
if (ID == Integer.MAX_VALUE)
ID = Integer.MIN_VALUE;
ID++;
// time = System.currentTimeMillis();
// timers.put(player, System.currentTimeMillis());
}
public FireBlast(Location location, Vector direction, Player player,
int damage, List<Block> safeblocks) {
if (location.getBlock().isLiquid()) {
return;
}
safe = safeblocks;
range = Methods.firebendingDayAugment(range, player.getWorld());
// timers.put(player, System.currentTimeMillis());
this.player = player;
this.location = location.clone();
origin = location.clone();
this.direction = direction.clone().normalize();
this.damage *= 1.5;
id = ID;
instances.put(id, this);
if (ID == Integer.MAX_VALUE)
ID = Integer.MIN_VALUE;
ID++;
}
public boolean progress() {
if (player.isDead() || !player.isOnline()) {
instances.remove(id);
return false;
}
// if (Methods.isRegionProtectedFromBuild(player, Abilities.Blaze, location)) {
// instances.remove(id);
// return false;
// }
speedfactor = speed * (ProjectKorra.time_step / 1000.);
ticks++;
if (ticks > maxticks) {
instances.remove(id);
return false;
}
// if (player.isSneaking()
// && Methods.getBendingAbility(player) == Abilities.AirBlast) {
// new AirBlast(player);
// }
Block block = location.getBlock();
// for (Block testblock : Methods.getBlocksAroundPoint(location,
// affectingradius)) {
// if (testblock.getType() == Material.FIRE) {
// testblock.setType(Material.AIR);
// testblock.getWorld().playEffect(testblock.getLocation(),
// Effect.EXTINGUISH, 0);
// }
// if (((block.getType() == Material.LEVER) || (block.getType() ==
// Material.STONE_BUTTON))
// && !affectedlevers.contains(block)) {
// EntityHuman eH = ((CraftPlayer) player).getHandle();
//
// net.minecraft.server.Block.byId[block.getTypeId()].interact(
// ((CraftWorld) block.getWorld()).getHandle(),
// block.getX(), block.getY(), block.getZ(), eH);
//
// affectedlevers.add(block);
// }
// }
if (Methods.isSolid(block) || block.isLiquid()) {
if (block.getType() == Material.FURNACE && canPowerFurnace) {
// BlockState state = block.getState();
// Furnace furnace = (Furnace) state;
// FurnaceInventory inv = furnace.getInventory();
// if (inv.getFuel() == null) {
// ItemStack temp = inv.getSmelting();
// ItemStack tempfuel = new ItemStack(Material.WOOD_AXE, 1);
// ItemStack tempsmelt = new ItemStack(Material.COBBLESTONE);
// inv.setFuel(tempfuel);
// inv.setSmelting(tempsmelt);
// state.update(true);
// inv.setSmelting(temp);
// state.update(true);
// }
} else if (FireStream.isIgnitable(player,
block.getRelative(BlockFace.UP))) {
ignite(location);
}
instances.remove(id);
return false;
}
if (location.distance(origin) > range) {
instances.remove(id);
return false;
}
Methods.removeSpouts(location, player);
double radius = FireBlast.affectingradius;
Player source = player;
if (EarthBlast.annihilateBlasts(location, radius, source)
|| WaterManipulation.annihilateBlasts(location, radius, source)
|| FireBlast.annihilateBlasts(location, radius, source)) {
instances.remove(id);
return false;
}
for (Entity entity : Methods.getEntitiesAroundPoint(location,
affectingradius)) {
// Block bblock = location.getBlock();
// Block block1 = entity.getLocation().getBlock();
// if (bblock.equals(block1))
affect(entity);
if (entity instanceof LivingEntity) {
// Block block2 = ((LivingEntity) entity).getEyeLocation()
// .getBlock();
// if (bblock.equals(block1))
// break;
// if (bblock.equals(block2)) {
// affect(entity);
break;
// }
}
}
advanceLocation();
return true;
}
private void advanceLocation() {
location.getWorld().playEffect(location, Effect.MOBSPAWNER_FLAMES, 0,
(int) range);
location = location.add(direction.clone().multiply(speedfactor));
}
private void ignite(Location location) {
for (Block block : Methods
.getBlocksAroundPoint(location, affectingradius)) {
if (FireStream.isIgnitable(player, block) && !safe.contains(block)) {
if (Methods.isPlant(block))
new Plantbending(block);
block.setType(Material.FIRE);
if (dissipate) {
FireStream.ignitedblocks.put(block, player);
FireStream.ignitedtimes.put(block,
System.currentTimeMillis());
}
}
}
}
public static boolean progress(int ID) {
if (instances.containsKey(ID))
return instances.get(ID).progress();
return false;
}
public static void progressAll() {
for (int id : instances.keySet()) {
progress(id);
}
}
private void affect(Entity entity) {
if (entity.getEntityId() != player.getEntityId()) {
if (AvatarState.isAvatarState(player)) {
entity.setVelocity(direction.clone().multiply(
AvatarState.getValue(pushfactor)));
} else {
entity.setVelocity(direction.clone().multiply(pushfactor));
}
if (entity instanceof LivingEntity) {
entity.setFireTicks(50);
Methods.damageEntity(player, entity, (int) Methods
.firebendingDayAugment((double) damage,
entity.getWorld()));
new Enflamed(entity, player);
instances.remove(id);
}
}
}
public static void removeFireBlastsAroundPoint(Location location,
double radius) {
for (int id : instances.keySet()) {
Location fireblastlocation = instances.get(id).location;
if (location.getWorld() == fireblastlocation.getWorld()) {
if (location.distance(fireblastlocation) <= radius)
instances.remove(id);
}
}
Fireball.removeFireballsAroundPoint(location, radius);
}
public static boolean annihilateBlasts(Location location, double radius,
Player source) {
boolean broke = false;
for (int id : instances.keySet()) {
FireBlast blast = instances.get(id);
Location fireblastlocation = blast.location;
if (location.getWorld() == fireblastlocation.getWorld()
&& !blast.player.equals(source)) {
if (location.distance(fireblastlocation) <= radius) {
instances.remove(id);
broke = true;
}
}
}
if (Fireball.annihilateBlasts(location, radius, source))
broke = true;
return broke;
}
public static void removeAll() {
for (int id : instances.keySet()) {
instances.remove(id);
}
}
public static String getDescription() {
return "FireBlast is the most fundamental bending technique of a firebender. "
+ "To use, simply left-click in a direction. A blast of fire will be created at your fingertips. "
+ "If this blast contacts an enemy, it will dissipate and engulf them in flames, "
+ "doing additional damage and knocking them back slightly. "
+ "If the blast hits terrain, it will ignite the nearby area. "
+ "Additionally, if you hold sneak, you will charge up the fireblast. "
+ "If you release it when it's charged, it will instead launch a powerful "
+ "fireball that explodes on contact.";
}
}

View file

@ -0,0 +1,297 @@
package com.projectkorra.ProjectKorra.firebending;
import java.util.concurrent.ConcurrentHashMap;
import org.bukkit.Effect;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.entity.TNTPrimed;
import org.bukkit.util.Vector;
import com.projectkorra.ProjectKorra.Methods;
import com.projectkorra.ProjectKorra.ProjectKorra;
import com.projectkorra.ProjectKorra.Ability.AvatarState;
public class Fireball {
public static ConcurrentHashMap<Integer, Fireball> instances = new ConcurrentHashMap<Integer, Fireball>();
private static ConcurrentHashMap<Entity, Fireball> explosions = new ConcurrentHashMap<Entity, Fireball>();
private static long defaultchargetime = 2000;
private static long interval = 25;
private static double radius = 1.5;
private static int ID = Integer.MIN_VALUE;
private int id;
private double range = 20;
private int maxdamage = 4;
private double explosionradius = 6;
private double innerradius = 3;
private Player player;
private Location origin;
private Location location;
private Vector direction;
private long starttime;
private long time;
private long chargetime = defaultchargetime;
private boolean charged = false;
private boolean launched = false;
private TNTPrimed explosion = null;
public Fireball(Player player) {
this.player = player;
time = System.currentTimeMillis();
starttime = time;
if (Methods.isDay(player.getWorld())) {
chargetime = (long) (chargetime / ProjectKorra.plugin.getConfig().getDouble("Properties.Fire.DayFactor"));
}
if (AvatarState.isAvatarState(player)) {
chargetime = 0;
maxdamage = AvatarState.getValue(maxdamage);
}
range = Methods.firebendingDayAugment(range, player.getWorld());
if (!player.getEyeLocation().getBlock().isLiquid()) {
id = ID;
instances.put(id, this);
if (ID == Integer.MAX_VALUE)
ID = Integer.MIN_VALUE;
ID++;
}
}
private void progress() {
if (Methods.getBoundAbility(player) == null) {
remove();
return;
}
if (!Methods.canBend(player.getName(), "FireBlast") && !launched) {
remove();
return;
}
if (!Methods.getBoundAbility(player).equalsIgnoreCase("FireBlast") && !launched) {
remove();
return;
}
if (System.currentTimeMillis() > starttime + chargetime) {
charged = true;
}
if (!player.isSneaking() && !charged) {
new FireBlast(player);
remove();
return;
}
if (!player.isSneaking() && !launched) {
launched = true;
location = player.getEyeLocation();
origin = location.clone();
direction = location.getDirection().normalize().multiply(radius);
}
if (System.currentTimeMillis() > time + interval) {
if (launched) {
// if (Methods.isRegionProtectedFromBuild(player, Abilities.Blaze,
// location)) {
// remove();
// return;
// }
}
time = System.currentTimeMillis();
if (!launched && !charged)
return;
if (!launched) {
player.getWorld().playEffect(player.getEyeLocation(),
Effect.MOBSPAWNER_FLAMES, 0, 3);
return;
}
location = location.clone().add(direction);
if (location.distance(origin) > range) {
remove();
return;
}
if (Methods.isSolid(location.getBlock())) {
explode();
return;
} else if (location.getBlock().isLiquid()) {
remove();
return;
}
fireball();
}
}
public static Fireball getFireball(Entity entity) {
if (explosions.containsKey(entity))
return explosions.get(entity);
return null;
}
public void dealDamage(Entity entity) {
if (explosion == null)
return;
// if (Methods.isObstructed(explosion.getLocation(),
// entity.getLocation())) {
// return 0;
// }
double distance = entity.getLocation()
.distance(explosion.getLocation());
if (distance > explosionradius)
return;
if (distance < innerradius) {
Methods.damageEntity(player, entity, maxdamage);
return;
}
double slope = -(maxdamage * .5) / (explosionradius - innerradius);
double damage = slope * (distance - innerradius) + maxdamage;
// Methods.verbose(damage);
Methods.damageEntity(player, entity, (int) damage);
}
private void fireball() {
for (Block block : Methods.getBlocksAroundPoint(location, radius)) {
block.getWorld().playEffect(block.getLocation(),
Effect.MOBSPAWNER_FLAMES, 0, 20);
}
for (Entity entity : Methods.getEntitiesAroundPoint(location, 2 * radius)) {
if (entity.getEntityId() == player.getEntityId())
continue;
entity.setFireTicks(120);
if (entity instanceof LivingEntity) {
explode();
return;
}
}
}
public static boolean isCharging(Player player) {
for (int id : instances.keySet()) {
Fireball ball = instances.get(id);
if (ball.player == player && !ball.launched)
return true;
}
return false;
}
private void explode() {
// List<Block> blocks = Methods.getBlocksAroundPoint(location, 3);
// List<Block> blocks2 = new ArrayList<Block>();
// Methods.verbose("Fireball Explode!");
boolean explode = true;
for (Block block : Methods.getBlocksAroundPoint(location, 3)) {
// if (Methods.isRegionProtectedFromBuild(player, Abilities.FireBlast,
// block.getLocation())) {
// explode = false;
// break;
// }
}
if (explode) {
explosion = player.getWorld().spawn(location, TNTPrimed.class);
explosion.setFuseTicks(0);
float yield = 1;
switch (player.getWorld().getDifficulty()) {
case PEACEFUL:
yield *= 2.;
break;
case EASY:
yield *= 2.;
break;
case NORMAL:
yield *= 1.;
break;
case HARD:
yield *= 3. / 4.;
break;
}
explosion.setYield(yield);
explosions.put(explosion, this);
}
// location.getWorld().createExplosion(location, 1);
ignite(location);
remove();
}
private void ignite(Location location) {
for (Block block : Methods.getBlocksAroundPoint(location,
FireBlast.affectingradius)) {
if (FireStream.isIgnitable(player, block)) {
block.setType(Material.FIRE);
if (FireBlast.dissipate) {
FireStream.ignitedblocks.put(block, player);
FireStream.ignitedtimes.put(block,
System.currentTimeMillis());
}
}
}
}
public static void progressAll() {
for (int id : instances.keySet())
instances.get(id).progress();
}
private void remove() {
instances.remove(id);
}
public static void removeAll() {
for (int id : instances.keySet())
instances.get(id).remove();
}
public static void removeFireballsAroundPoint(Location location,
double radius) {
for (int id : instances.keySet()) {
Fireball fireball = instances.get(id);
if (!fireball.launched)
continue;
Location fireblastlocation = fireball.location;
if (location.getWorld() == fireblastlocation.getWorld()) {
if (location.distance(fireblastlocation) <= radius)
instances.remove(id);
}
}
}
public static boolean annihilateBlasts(Location location, double radius,
Player source) {
boolean broke = false;
for (int id : instances.keySet()) {
Fireball fireball = instances.get(id);
if (!fireball.launched)
continue;
Location fireblastlocation = fireball.location;
if (location.getWorld() == fireblastlocation.getWorld()
&& !source.equals(fireball.player)) {
if (location.distance(fireblastlocation) <= radius) {
fireball.explode();
broke = true;
}
}
}
return broke;
}
}

View file

@ -17,6 +17,7 @@ import com.projectkorra.ProjectKorra.Methods;
import com.projectkorra.ProjectKorra.ProjectKorra;
import com.projectkorra.ProjectKorra.TempBlock;
import com.projectkorra.ProjectKorra.Ability.AvatarState;
import com.projectkorra.ProjectKorra.firebending.FireBlast;
public class WaterManipulation {

View file

@ -14,6 +14,7 @@ import com.projectkorra.ProjectKorra.Methods;
import com.projectkorra.ProjectKorra.ProjectKorra;
import com.projectkorra.ProjectKorra.TempBlock;
import com.projectkorra.ProjectKorra.Ability.AvatarState;
import com.projectkorra.ProjectKorra.firebending.FireBlast;
public class WaterWall {

View file

@ -16,6 +16,7 @@ import com.projectkorra.ProjectKorra.Methods;
import com.projectkorra.ProjectKorra.ProjectKorra;
import com.projectkorra.ProjectKorra.TempBlock;
import com.projectkorra.ProjectKorra.Ability.AvatarState;
import com.projectkorra.ProjectKorra.firebending.FireBlast;
public class Wave {

View file

@ -153,6 +153,16 @@ Abilities:
Height: 6
Width: 6
Fire:
FireBlast:
Enabled: true
Description: "FireBlast is the most fundamental technique of a firebender. To use, simply left-click in a direction. A blast of fire will be created at your fingertips. If this blast contacts an enemy, it will dissipate and engulf them in flames, doing additional damage and knocking them back slightly. If the blast hits terrain, it will ignite the nearby area. Additionally, if you hold sneak, you will charge up the fireblast. If you release it when it's charged, it will instead launch a powerful fireball that explodes on contact."
Speed: 15
Range: 15
Radius: 2
Push: 0.3
Damage: 2
Cooldown: 1500
Dissipate: false
FireJet:
Enabled: true
Description: "This ability is used for a limited burst of flight for firebenders. Clicking with this ability selected will launch you in the direction you're looking, granting you controlled flight for a short time. This ability can be used mid-air to prevent falling to your death, but on the ground it can only be used if standing on a block that's ignitable (e.g. not snow or water)."