TF-ProjectKorra/src/com/projectkorra/projectkorra/firebending/FireBlast.java
Vahagn Tovmasian 3c1d6b7b85
Blue Fire Update & Firebending Refactor (#1062)
## Additions
* Adds Blue Fire SubElement.
    > *Adds related damage, cooldown, and range modifiers for configuration
* Adds Sticks, Sponges, and Chorus Fruit to cookable HeatControl items.
* Adds Smoker, BlastFurnace, and extinguished Campfires to blocks which FireBlast can light.
* Adds new TempBlock constructor which takes in a `long revertTime` parameter
* Adds new blocks to block lists in configuration
  >* Adds new nether plants to plantBlocks list
  >* Adds new earth blocks to earthBlocks list

## Fixes
* Fixes AvatarState buffs overriding day related buffs for firebending.
* Fixes Blaze not going up hills, going through walls (mostly), jumping gaps.
* Fixes Furnaces and related blocks not smelting after being activated by FireBlast

## Removals
* Removes BlazeArc dependencies for Fire Abilities which ignite the ground.
* Removes smoke particles from Fire bending to increase visibility and better emulate the show.

## Misc. Changes
* Changes API versioning to 1.16.1
* Fire from Firebending no longer reverts all at once.
* Changes Combustion animation to be more beam-like rather than a rehash of FireBlast.
* Changes Add, Remove, Display command to properly display space for Blue Fire.
* Changes `ElementalAbility#isFire()` to check for SOUL_FIRE_FLAME.
* Changes isIgnitable to check whether fire can be placed at that location rather than solely based on flammability.
* Changes firebending abilities to use `FireAbility#playFirebendingParticles()` & `FireAbility#createTempFire()` where applicable.
* Changes `FireAbility#playFirebendingParticles()` to play blue fire particles when player has the BlueFire subelement.
2020-07-11 22:05:45 -07:00

460 lines
13 KiB
Java

package com.projectkorra.projectkorra.firebending;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.BlastFurnace;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.data.type.Campfire;
import org.bukkit.block.Furnace;
import org.bukkit.block.Smoker;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.util.Vector;
import com.projectkorra.projectkorra.GeneralMethods;
import com.projectkorra.projectkorra.ProjectKorra;
import com.projectkorra.projectkorra.Element.SubElement;
import com.projectkorra.projectkorra.ability.AirAbility;
import com.projectkorra.projectkorra.ability.BlueFireAbility;
import com.projectkorra.projectkorra.ability.FireAbility;
import com.projectkorra.projectkorra.ability.util.Collision;
import com.projectkorra.projectkorra.attribute.Attribute;
import com.projectkorra.projectkorra.avatar.AvatarState;
import com.projectkorra.projectkorra.command.Commands;
import com.projectkorra.projectkorra.firebending.util.FireDamageTimer;
import com.projectkorra.projectkorra.util.DamageHandler;
import com.projectkorra.projectkorra.waterbending.plant.PlantRegrowth;
public class FireBlast extends FireAbility {
private static final int MAX_TICKS = 10000;
@Attribute("PowerFurnace")
private boolean powerFurnace;
private boolean showParticles;
private boolean dissipate;
private boolean isFireBurst = false;
private boolean fireBurstIgnite;
private int ticks;
@Attribute(Attribute.COOLDOWN)
private long cooldown;
private double speedFactor;
@Attribute(Attribute.RANGE)
private double range;
@Attribute(Attribute.DAMAGE)
private double damage;
@Attribute(Attribute.SPEED)
private double speed;
private double collisionRadius;
@Attribute(Attribute.FIRE_TICK)
private double fireTicks;
@Attribute(Attribute.KNOCKBACK)
private double knockback;
private double flameRadius;
private Random random;
private Location location;
private Location origin;
private Vector direction;
private List<Block> safeBlocks;
public FireBlast(final Location location, final Vector direction, final Player player, final int damage, final List<Block> safeBlocks) {
super(player);
if (location.getBlock().isLiquid()) {
return;
}
this.setFields();
this.safeBlocks = safeBlocks;
this.location = location.clone();
this.origin = location.clone();
this.direction = direction.clone().normalize();
// The following code determines the total additive modifier between Blue Fire & Day Modifiers
this.applyModifiers();
this.start();
}
public FireBlast(final Player player) {
super(player);
if (this.bPlayer.isOnCooldown("FireBlast")) {
return;
} else if (player.getEyeLocation().getBlock().isLiquid() || FireBlastCharged.isCharging(player)) {
return;
}
this.setFields();
this.isFireBurst = false;
this.damage = getConfig().getDouble("Abilities.Fire.FireBlast.Damage");
this.safeBlocks = new ArrayList<>();
this.location = player.getEyeLocation();
this.origin = player.getEyeLocation();
this.direction = player.getEyeLocation().getDirection().normalize();
this.location = this.location.add(this.direction.clone());
// The following code determines the total additive modifier between Blue Fire & Day Modifiers
this.applyModifiers();
this.start();
this.bPlayer.addCooldown("FireBlast", this.cooldown);
}
private void applyModifiers() {
int damageMod = 0;
int rangeMod = 0;
damageMod = (int) (this.getDayFactor(damage) - damage);
rangeMod = (int) (this.getDayFactor(this.range) - this.range);
damageMod = (int) (bPlayer.canUseSubElement(SubElement.BLUE_FIRE) ? (BlueFireAbility.getDamageFactor() * damage - damage) + damageMod : damageMod);
rangeMod = (int) (bPlayer.canUseSubElement(SubElement.BLUE_FIRE) ? (BlueFireAbility.getRangeFactor() * range - range) + rangeMod : rangeMod);
this.range += rangeMod;
this.damage += damageMod;
}
private void setFields() {
this.isFireBurst = true;
this.powerFurnace = true;
this.showParticles = true;
this.fireBurstIgnite = getConfig().getBoolean("Abilities.Fire.FireBurst.Ignite");
this.dissipate = getConfig().getBoolean("Abilities.Fire.FireBlast.Dissipate");
this.cooldown = getConfig().getLong("Abilities.Fire.FireBlast.Cooldown");
this.range = getConfig().getDouble("Abilities.Fire.FireBlast.Range");
this.speed = getConfig().getDouble("Abilities.Fire.FireBlast.Speed");
this.collisionRadius = getConfig().getDouble("Abilities.Fire.FireBlast.CollisionRadius");
this.fireTicks = getConfig().getDouble("Abilities.Fire.FireBlast.FireTicks");
this.knockback = getConfig().getDouble("Abilities.Fire.FireBlast.Knockback");
this.flameRadius = getConfig().getDouble("Abilities.Fire.FireBlast.FlameParticleRadius");
this.random = new Random();
}
private void advanceLocation() {
if (this.isFireBurst) {
this.flameRadius += 0.06;
}
if (this.showParticles) {
playFirebendingParticles(this.location, 6, this.flameRadius, this.flameRadius, this.flameRadius);
}
if (GeneralMethods.checkDiagonalWall(this.location, this.direction)) {
this.remove();
return;
}
this.location = this.location.add(this.direction.clone().multiply(this.speedFactor));
if (this.random.nextInt(4) == 0) {
playFirebendingSound(this.location);
}
}
private void affect(final Entity entity) {
if (entity.getUniqueId() != this.player.getUniqueId() && !GeneralMethods.isRegionProtectedFromBuild(this, entity.getLocation()) && !((entity instanceof Player) && Commands.invincible.contains(((Player) entity).getName()))) {
if (this.bPlayer.isAvatarState()) {
GeneralMethods.setVelocity(entity, this.direction.clone().multiply(AvatarState.getValue(this.knockback)));
} else {
GeneralMethods.setVelocity(entity, this.direction.clone().multiply(this.knockback));
}
if (entity instanceof LivingEntity) {
entity.setFireTicks((int) (this.fireTicks * 20));
DamageHandler.damageEntity(entity, this.damage, this);
AirAbility.breakBreathbendingHold(entity);
new FireDamageTimer(entity, this.player);
this.remove();
}
}
}
private void ignite(final Location location) {
for (final Block block : GeneralMethods.getBlocksAroundPoint(location, this.collisionRadius)) {
if (isIgnitable(block) && !this.safeBlocks.contains(block) && !GeneralMethods.isRegionProtectedFromBuild(this, block.getLocation())) {
if (canFireGrief()) {
if (isPlant(block) || isSnow(block)) {
new PlantRegrowth(this.player, block);
}
}
createTempFire(block.getLocation());
}
}
}
@Override
public void progress() {
if (!this.bPlayer.canBendIgnoreBindsCooldowns(this) || GeneralMethods.isRegionProtectedFromBuild(this, this.location)) {
this.remove();
return;
}
this.speedFactor = this.speed * (ProjectKorra.time_step / 1000.0);
this.ticks++;
if (this.ticks > MAX_TICKS) {
this.remove();
return;
}
final Block block = this.location.getBlock();
if (GeneralMethods.isSolid(block) || block.isLiquid()) {
if (block.getType() == Material.FURNACE && this.powerFurnace) {
final Furnace furnace = (Furnace) block.getState();
furnace.setBurnTime((short) 800);
furnace.update();
} else if (block.getType() == Material.SMOKER && this.powerFurnace) {
final Smoker smoker = (Smoker) block.getState();
smoker.setBurnTime((short) 800);
smoker.update();
} else if (block.getType() == Material.BLAST_FURNACE && this.powerFurnace) {
final BlastFurnace blastF = (BlastFurnace) block.getState();
blastF.setBurnTime((short) 800);
blastF.update();
} else if (block instanceof Campfire) {
final Campfire campfire = (Campfire) block.getBlockData();
if(!campfire.isLit()) {
if(block.getType() != Material.SOUL_CAMPFIRE || bPlayer.canUseSubElement(SubElement.BLUE_FIRE)) {
campfire.setLit(true);
}
}
} else if (isIgnitable(block.getRelative(BlockFace.UP))) {
if ((this.isFireBurst && this.fireBurstIgnite) || !this.isFireBurst) {
this.ignite(this.location);
}
}
this.remove();
return;
}
if (this.location.distanceSquared(this.origin) > this.range * this.range) {
this.remove();
return;
}
Entity entity = GeneralMethods.getClosestEntity(this.location, this.collisionRadius);
if (entity != null) {
this.affect(entity);
}
this.advanceLocation();
}
/**
* This method was used for the old collision detection system. Please see
* {@link Collision} for the new system.
*/
@Deprecated
public static boolean annihilateBlasts(final Location location, final double radius, final Player source) {
boolean broke = false;
for (final FireBlast blast : getAbilities(FireBlast.class)) {
final Location fireBlastLocation = blast.location;
if (location.getWorld().equals(fireBlastLocation.getWorld()) && !blast.player.equals(source)) {
if (location.distanceSquared(fireBlastLocation) <= radius * radius) {
blast.remove();
broke = true;
}
}
}
if (FireBlastCharged.annihilateBlasts(location, radius, source)) {
broke = true;
}
return broke;
}
public static ArrayList<FireBlast> getAroundPoint(final Location location, final double radius) {
final ArrayList<FireBlast> list = new ArrayList<FireBlast>();
for (final FireBlast fireBlast : getAbilities(FireBlast.class)) {
final Location fireblastlocation = fireBlast.location;
if (location.getWorld().equals(fireblastlocation.getWorld())) {
if (location.distanceSquared(fireblastlocation) <= radius * radius) {
list.add(fireBlast);
}
}
}
return list;
}
public static void removeFireBlastsAroundPoint(final Location location, final double radius) {
for (final FireBlast fireBlast : getAbilities(FireBlast.class)) {
final Location fireBlastLocation = fireBlast.location;
if (location.getWorld().equals(fireBlastLocation.getWorld())) {
if (location.distanceSquared(fireBlastLocation) <= radius * radius) {
fireBlast.remove();
}
}
}
FireBlastCharged.removeFireballsAroundPoint(location, radius);
}
@Override
public String getName() {
return this.isFireBurst ? "FireBurst" : "FireBlast";
}
@Override
public Location getLocation() {
return this.location != null ? this.location : this.origin;
}
@Override
public long getCooldown() {
return this.cooldown;
}
@Override
public boolean isSneakAbility() {
return true;
}
@Override
public boolean isHarmlessAbility() {
return false;
}
@Override
public double getCollisionRadius() {
return this.collisionRadius;
}
public boolean isPowerFurnace() {
return this.powerFurnace;
}
public void setPowerFurnace(final boolean powerFurnace) {
this.powerFurnace = powerFurnace;
}
public boolean isShowParticles() {
return this.showParticles;
}
public void setShowParticles(final boolean showParticles) {
this.showParticles = showParticles;
}
public boolean isDissipate() {
return this.dissipate;
}
public void setDissipate(final boolean dissipate) {
this.dissipate = dissipate;
}
public int getTicks() {
return this.ticks;
}
public void setTicks(final int ticks) {
this.ticks = ticks;
}
public double getSpeedFactor() {
return this.speedFactor;
}
public void setSpeedFactor(final double speedFactor) {
this.speedFactor = speedFactor;
}
public double getRange() {
return this.range;
}
public void setRange(final double range) {
this.range = range;
}
public double getDamage() {
return this.damage;
}
public void setDamage(final double damage) {
this.damage = damage;
}
public double getSpeed() {
return this.speed;
}
public void setSpeed(final double speed) {
this.speed = speed;
}
public void setCollisionRadius(final double collisionRadius) {
this.collisionRadius = collisionRadius;
}
public double getFireTicks() {
return this.fireTicks;
}
public void setFireTicks(final double fireTicks) {
this.fireTicks = fireTicks;
}
public double getPushFactor() {
return this.knockback;
}
public void setPushFactor(final double pushFactor) {
this.knockback = pushFactor;
}
public Random getRandom() {
return this.random;
}
public void setRandom(final Random random) {
this.random = random;
}
public Location getOrigin() {
return this.origin;
}
public void setOrigin(final Location origin) {
this.origin = origin;
}
public Vector getDirection() {
return this.direction;
}
public void setDirection(final Vector direction) {
this.direction = direction;
}
public static int getMaxTicks() {
return MAX_TICKS;
}
public List<Block> getSafeBlocks() {
return this.safeBlocks;
}
public void setCooldown(final long cooldown) {
this.cooldown = cooldown;
}
public void setLocation(final Location location) {
this.location = location;
}
public boolean isFireBurst() {
return this.isFireBurst;
}
public void setFireBurst(final boolean isFireBurst) {
this.isFireBurst = isFireBurst;
}
}