TF-ProjectKorra/src/com/projectkorra/projectkorra/earthbending/EarthBlast.java
Christopher Martin 787b303c9f
1.9.1 (#1145)
## Additions
* Adds a built-in bending board sidebar to visualize bound abilities and cooldowns.
  * The board respects worlds where bending is disabled.
  * Players can use the command `/pk board` to toggle the visibility of their board.
  * Includes an API that community developers can use in BendingBoardManager.
  * Adds the `"Properties.BendingBoard"` config option to enable or disable the board server.
  * Adds language file configuration options to control BendingBoard visuals.
    * `"Board.Title"`
      * Controls the title at the top of the board.
      * Supports the standard Minecraft color codes.
    * `"Board.SelectionPrefix"`
      * Controls the prefix shown corresponding to your current hot bar slot.
      * Supports the standard Minecraft color codes.
    * `"Board.EmptySlot"`
      * Controls what is shown for empty slots.
      * Supports the standard Minecraft color codes.
      * `{slot_number}` can be used as a placeholder for the slot number.
    * `"Board.MiscSeparator"`
      * Controls the separation between hot bar binds and temporary cooldowns such as Combos.
      * Supports the standard Minecraft color codes.
* Adds support for KingdomsX version 1.10.19.1
* Adds ability permission check to passive abilities. They should now respect their `bending.ability.<ability name>` permissions.
* Adds `AbilityVelocityAffectEntityEvent`
  * A cancellable event that will fire whenever an ability would alter the velocity of an entity.
* Adds the `Abilities.Earth.EarthSmash.Shoot.CollisionRadius` configuration option
  * Sets the collision radius of shot EarthSmash.

## Fixes
* Fixes FireBlast going through liquids.
* Fixes duplication involving waterlogged containers.
* Fixes being able to not enter the name of a Preset when using the `/pk preset create <name>` command.
* Fixes getDayFactor() not being applied correctly and occasionally producing the wrong value.
* Fixes a rounding issue with some Fire ability damage configuration options.
* Fixes an error when attempting to start EarthGrab.
* Fixes PhaseChange error when melting snow.
* Fixes a memory/process leak in how cooldowns were removed.
  * A player's cooldowns could only be removed when they were online. If a player's cooldown expired while they weren't online, their cooldown would attempt to revert every tick until the player rejoined. This has been resolved so cooldowns can revert while a player is offline.
  * A side effect of this fix is that it is now possible for `PlayerCooldownChangeEvents` to fire while their corresponding Player is offline.
* Fixes an issue with `MultiAbilityManager#hasMultiAbilityBound` where it would return true if any MultiAbility is bound, not if the specified MultiAbility was bound.

## Misc Changes
* Updates Towny version to 0.96.2.0
* DensityShift sand blocks can now be used as a bendable source.
* Changes AvatarState so that its cooldown is applied when the ability ends instead of when it starts.
* Changes togglable abilities such as AvatarState, Illumination, and TremorSense to visually show when they are enabled in the BendingBoard and on BendingPreview in the same way as the ChiBlocking Stances.
* Updated the text of some ability descriptions and instructions.
* Adds new cache to PhaseChange to greatly improve the performance of water/ice updates.
2021-06-07 16:58:29 -07:00

683 lines
20 KiB
Java

package com.projectkorra.projectkorra.earthbending;
import java.util.ArrayList;
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.BendingPlayer;
import com.projectkorra.projectkorra.GeneralMethods;
import com.projectkorra.projectkorra.ability.AirAbility;
import com.projectkorra.projectkorra.ability.EarthAbility;
import com.projectkorra.projectkorra.ability.util.Collision;
import com.projectkorra.projectkorra.attribute.Attribute;
import com.projectkorra.projectkorra.earthbending.passive.DensityShift;
import com.projectkorra.projectkorra.util.BlockSource;
import com.projectkorra.projectkorra.util.ClickType;
import com.projectkorra.projectkorra.util.DamageHandler;
import com.projectkorra.projectkorra.util.TempBlock;
public class EarthBlast extends EarthAbility {
private boolean isProgressing;
private boolean isAtDestination;
private boolean isSettingUp;
private boolean canHitSelf;
private long time;
private long interval;
@Attribute(Attribute.COOLDOWN)
private long cooldown;
@Attribute(Attribute.RANGE)
private double range;
@Attribute(Attribute.DAMAGE)
private double damage;
@Attribute(Attribute.SPEED)
private double speed;
@Attribute(Attribute.KNOCKBACK)
private double pushFactor;
@Attribute(Attribute.SELECT_RANGE)
private double selectRange;
@Attribute("DeflectRange")
private double deflectRange;
private double collisionRadius;
private Material sourceType;
private Location location;
private Location destination;
private Location firstDestination;
private Block sourceBlock;
public EarthBlast(final Player player) {
super(player);
this.isProgressing = false;
this.isAtDestination = false;
this.isSettingUp = true;
this.deflectRange = getConfig().getDouble("Abilities.Earth.EarthBlast.DeflectRange");
this.collisionRadius = getConfig().getDouble("Abilities.Earth.EarthBlast.CollisionRadius");
this.cooldown = getConfig().getLong("Abilities.Earth.EarthBlast.Cooldown");
this.canHitSelf = getConfig().getBoolean("Abilities.Earth.EarthBlast.CanHitSelf");
this.range = getConfig().getDouble("Abilities.Earth.EarthBlast.Range");
this.damage = getConfig().getDouble("Abilities.Earth.EarthBlast.Damage");
this.speed = getConfig().getDouble("Abilities.Earth.EarthBlast.Speed");
this.pushFactor = getConfig().getDouble("Abilities.Earth.EarthBlast.Push");
this.selectRange = getConfig().getDouble("Abilities.Earth.EarthBlast.SelectRange");
this.time = System.currentTimeMillis();
this.interval = (long) (1000.0 / this.speed);
if (this.bPlayer.isAvatarState()) {
this.cooldown = getConfig().getLong("Abilities.Avatar.AvatarState.Earth.EarthBlast.Cooldown");
this.damage = getConfig().getDouble("Abilities.Avatar.AvatarState.Earth.EarthBlast.Damage");
}
if (this.prepare()) {
this.start();
this.time = System.currentTimeMillis();
}
}
private void checkForCollision() {
for (final EarthBlast blast : getAbilities(EarthBlast.class)) {
if (blast.player.equals(this.player)) {
continue;
} else if (!blast.location.getWorld().equals(this.player.getWorld())) {
continue;
} else if (!blast.isProgressing) {
continue;
} else if (GeneralMethods.isRegionProtectedFromBuild(this, blast.location)) {
continue;
}
final Location location = this.player.getEyeLocation();
final Vector vector = location.getDirection();
final Location mloc = blast.location;
if (mloc.distanceSquared(location) <= this.range * this.range && GeneralMethods.getDistanceFromLine(vector, location, blast.location) < this.deflectRange && mloc.distanceSquared(location.clone().add(vector)) < mloc.distanceSquared(location.clone().add(vector.clone().multiply(-1)))) {
blast.remove();
this.remove();
return;
}
}
}
private void focusBlock() {
if (DensityShift.isPassiveSand(this.sourceBlock)) {
DensityShift.revertSand(this.sourceBlock);
}
if (this.sourceBlock.getType() == Material.SAND) {
this.sourceType = Material.SAND;
this.sourceBlock.setType(Material.SANDSTONE);
} else if (this.sourceBlock.getType() == Material.RED_SAND) {
this.sourceType = Material.RED_SAND;
this.sourceBlock.setType(Material.RED_SANDSTONE);
} else if (this.sourceBlock.getType() == Material.STONE) {
this.sourceBlock.setType(Material.COBBLESTONE);
this.sourceType = Material.STONE;
} else {
this.sourceType = this.sourceBlock.getType();
this.sourceBlock.setType(Material.STONE);
}
this.location = this.sourceBlock.getLocation();
}
private Location getTargetLocation() {
final Entity target = GeneralMethods.getTargetedEntity(this.player, this.range, new ArrayList<Entity>());
Location location;
final Material[] trans = new Material[getTransparentMaterials().length + this.getEarthbendableBlocks().size()];
int i = 0;
for (int j = 0; j < getTransparentMaterials().length; j++) {
trans[j] = getTransparentMaterials()[j];
i++;
}
for (int j = 0; j < this.getEarthbendableBlocks().size(); j++) {
try {
trans[i] = Material.valueOf(this.getEarthbendableBlocks().get(j));
} catch (final IllegalArgumentException e) {
continue;
}
i++;
}
if (target == null) {
location = GeneralMethods.getTargetedLocation(this.player, this.range, true, trans);
} else {
location = ((LivingEntity) target).getEyeLocation();
}
return location;
}
public boolean prepare() {
final Block block = BlockSource.getEarthSourceBlock(this.player, this.range, ClickType.SHIFT_DOWN);
if (block == null || !this.isEarthbendable(block)) {
return false;
} else if (TempBlock.isTempBlock(block) && !EarthAbility.isBendableEarthTempBlock(block)) {
return false;
}
boolean selectedABlockInUse = false;
for (final EarthBlast blast : getAbilities(this.player, EarthBlast.class)) {
if (!blast.isProgressing) {
blast.remove();
} else if (blast.isProgressing && block.equals(blast.sourceBlock)) {
selectedABlockInUse = true;
}
}
if (selectedABlockInUse) {
return false;
}
this.checkForCollision();
if (block.getLocation().distanceSquared(this.player.getLocation()) > this.selectRange * this.selectRange) {
return false;
}
this.sourceBlock = block;
this.focusBlock();
return true;
}
@Override
public void progress() {
if (!this.bPlayer.canBendIgnoreBindsCooldowns(this)) {
this.remove();
return;
}
if (System.currentTimeMillis() - this.time >= this.interval) {
this.time = System.currentTimeMillis();
if (this.isAtDestination) {
this.remove();
return;
} else if (!this.isEarthbendable(this.sourceBlock) && this.sourceBlock.getType() != Material.COBBLESTONE) {
this.remove();
return;
}
if (!this.isProgressing && !this.isAtDestination) {
if (this.sourceBlock == null || !this.bPlayer.getBoundAbilityName().equals(this.getName())) {
this.remove();
return;
} else if (!this.player.getWorld().equals(this.sourceBlock.getWorld())) {
this.remove();
return;
} else if (this.sourceBlock.getLocation().distanceSquared(this.player.getLocation()) > this.selectRange * this.selectRange) {
this.remove();
return;
}
}
if (this.isAtDestination) {
this.remove();
return;
} else {
if (!this.isProgressing) {
return;
}
if (this.sourceBlock.getY() == this.firstDestination.getBlockY()) {
this.isSettingUp = false;
}
Vector direction;
if (this.isSettingUp) {
direction = GeneralMethods.getDirection(this.location, this.firstDestination).normalize();
} else {
direction = GeneralMethods.getDirection(this.location, this.destination).normalize();
}
this.location = this.location.clone().add(direction);
Block block = this.location.getBlock();
if (block.getLocation().equals(this.sourceBlock.getLocation())) {
this.location = this.location.clone().add(direction);
block = this.location.getBlock();
}
if (this.isTransparent(block) && !block.isLiquid()) {
GeneralMethods.breakBlock(block);
} else if (!this.isSettingUp) {
this.remove();
return;
} else {
this.location = this.location.clone().subtract(direction);
direction = GeneralMethods.getDirection(this.location, this.destination).normalize();
this.location = this.location.clone().add(direction);
Block block2 = this.location.getBlock();
if (block2.getLocation().equals(this.sourceBlock.getLocation())) {
this.location = this.location.clone().add(direction);
block2 = this.location.getBlock();
}
if (this.isTransparent(block) && !block.isLiquid()) {
GeneralMethods.breakBlock(block);
} else {
this.remove();
return;
}
}
for (final Entity entity : GeneralMethods.getEntitiesAroundPoint(this.location, this.collisionRadius)) {
if (GeneralMethods.isRegionProtectedFromBuild(this, entity.getLocation())) {
continue;
}
if (entity instanceof LivingEntity && (entity.getEntityId() != this.player.getEntityId() || this.canHitSelf)) {
AirAbility.breakBreathbendingHold(entity);
final Location location = this.player.getEyeLocation();
final Vector vector = location.getDirection();
GeneralMethods.setVelocity(this, entity, vector.normalize().multiply(this.pushFactor));
double damage = this.damage;
if (isMetal(this.sourceBlock) && this.bPlayer.canMetalbend()) {
damage = getMetalAugment(damage);
}
DamageHandler.damageEntity(entity, damage, this);
this.isProgressing = false;
}
}
if (!this.isProgressing) {
this.remove();
return;
}
if (isEarthRevertOn()) {
this.sourceBlock.setType(this.sourceType);
moveEarthBlock(this.sourceBlock, block);
if (block.getType() == Material.SAND) {
block.setType(Material.SANDSTONE);
}
if (block.getType() == Material.GRAVEL) {
block.setType(Material.STONE);
}
} else {
block.setType(this.sourceType);
this.sourceBlock.setType(Material.AIR);
}
this.sourceBlock = block;
if (this.location.distanceSquared(this.destination) < 1) {
if (this.sourceType == Material.SAND || this.sourceType == Material.GRAVEL) {
this.isProgressing = false;
if (this.sourceBlock.getType() == Material.RED_SANDSTONE) {
this.sourceType = Material.SAND;
this.sourceBlock.setType(this.sourceType);
} else {
this.sourceBlock.setType(this.sourceType);
}
}
this.isAtDestination = true;
this.isProgressing = false;
}
return;
}
}
}
private void redirect(final Player player, final Location targetlocation) {
if (this.isProgressing) {
if (this.location.distanceSquared(player.getLocation()) <= this.range * this.range) {
this.isSettingUp = false;
this.destination = targetlocation;
}
}
}
@Override
public void remove() {
super.remove();
if (this.destination != null && this.sourceBlock != null) {
this.sourceBlock.setType(Material.AIR);
} else if (this.sourceBlock != null) {
this.sourceBlock.setType(this.sourceType);
}
}
public void throwEarth() {
if (this.sourceBlock == null || !this.sourceBlock.getWorld().equals(this.player.getWorld())) {
return;
}
if (getMovedEarth().containsKey(this.sourceBlock)) {
if (!isEarthRevertOn()) {
removeRevertIndex(this.sourceBlock);
}
}
final Entity target = GeneralMethods.getTargetedEntity(this.player, this.range, new ArrayList<Entity>());
if (target != null) {
this.destination = target.getLocation();
} else {
this.destination = this.getTargetLocation();
}
if (this.sourceBlock == null) {
return;
}
this.location = this.sourceBlock.getLocation();
if (this.destination.distanceSquared(this.location) < 1) {
return;
}
this.firstDestination = this.location.clone();
if (this.destination.getY() - this.location.getY() > 2) {
this.firstDestination.setY(this.destination.getY() - 1);
} else if (this.location.getY() > player.getEyeLocation().getY() && this.location.getBlock().getRelative(BlockFace.UP).isPassable()) {
this.firstDestination.subtract(0, 2, 0);
} else if (this.location.getBlock().getRelative(BlockFace.UP).isPassable() && this.location.getBlock().getRelative(BlockFace.UP, 2).isPassable()) {
this.firstDestination.add(0, 2, 0);
} else {
this.firstDestination.add(GeneralMethods.getDirection(this.location, this.destination).normalize().setY(0));
}
if (this.destination.distanceSquared(this.location) <= 1) {
this.isProgressing = false;
this.destination = null;
} else {
this.isProgressing = true;
playEarthbendingSound(this.sourceBlock.getLocation());
final Material currentType = this.sourceBlock.getType();
this.sourceBlock.setType(this.sourceType);
if (isEarthRevertOn()) {
addTempAirBlock(this.sourceBlock);
} else {
this.sourceBlock.breakNaturally();
}
this.sourceBlock.setType(currentType);
}
}
/**
* 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 EarthBlast blast : getAbilities(EarthBlast.class)) {
if (blast.location.getWorld().equals(location.getWorld()) && !source.equals(blast.player)) {
if (blast.location.distanceSquared(location) <= radius * radius) {
blast.remove();
broke = true;
}
}
}
return broke;
}
public static ArrayList<EarthBlast> getAroundPoint(final Location location, final double radius) {
final ArrayList<EarthBlast> list = new ArrayList<EarthBlast>();
for (final EarthBlast blast : getAbilities(EarthBlast.class)) {
if (blast.location.getWorld().equals(location.getWorld())) {
if (blast.location.distanceSquared(location) <= radius * radius) {
list.add(blast);
}
}
}
return list;
}
public static EarthBlast getBlastFromSource(final Block block) {
for (final EarthBlast blast : getAbilities(EarthBlast.class)) {
if (blast.sourceBlock.equals(block)) {
return blast;
}
}
return null;
}
private static void redirectTargettedBlasts(final Player player, final ArrayList<EarthBlast> ignore) {
for (final EarthBlast blast : getAbilities(EarthBlast.class)) {
if (!blast.isProgressing || ignore.contains(blast)) {
continue;
} else if (!blast.location.getWorld().equals(player.getWorld())) {
continue;
} else if (GeneralMethods.isRegionProtectedFromBuild(blast, blast.location)) {
continue;
} else if (blast.player.equals(player)) {
blast.redirect(player, blast.getTargetLocation());
}
final Location location = player.getEyeLocation();
final Vector vector = location.getDirection();
final Location mloc = blast.location;
if (mloc.distanceSquared(location) <= blast.range * blast.range && GeneralMethods.getDistanceFromLine(vector, location, blast.location) < blast.deflectRange && mloc.distanceSquared(location.clone().add(vector)) < mloc.distanceSquared(location.clone().add(vector.clone().multiply(-1)))) {
blast.redirect(player, blast.getTargetLocation());
}
}
}
public static void removeAroundPoint(final Location location, final double radius) {
for (final EarthBlast blast : getAbilities(EarthBlast.class)) {
if (blast.location.getWorld().equals(location.getWorld())) {
if (blast.location.distanceSquared(location) <= radius * radius) {
blast.remove();
}
}
}
}
public static void throwEarth(final Player player) {
final ArrayList<EarthBlast> ignore = new ArrayList<EarthBlast>();
final BendingPlayer bPlayer = BendingPlayer.getBendingPlayer(player);
EarthBlast earthBlast = null;
if (bPlayer == null) {
return;
}
for (final EarthBlast blast : getAbilities(player, EarthBlast.class)) {
if (!blast.isProgressing && bPlayer.canBend(blast)) {
blast.throwEarth();
ignore.add(blast);
earthBlast = blast;
}
}
if (earthBlast != null) {
bPlayer.addCooldown(earthBlast);
}
redirectTargettedBlasts(player, ignore);
}
@Override
public String getName() {
return "EarthBlast";
}
@Override
public Location getLocation() {
return this.location;
}
@Override
public long getCooldown() {
return this.cooldown;
}
@Override
public boolean isSneakAbility() {
return true;
}
@Override
public boolean isHarmlessAbility() {
return false;
}
@Override
public boolean isCollidable() {
return this.isProgressing;
}
@Override
public double getCollisionRadius() {
return this.collisionRadius;
}
public boolean isProgressing() {
return this.isProgressing;
}
public void setProgressing(final boolean isProgressing) {
this.isProgressing = isProgressing;
}
public boolean isAtDestination() {
return this.isAtDestination;
}
public void setAtDestination(final boolean isAtDestination) {
this.isAtDestination = isAtDestination;
}
public boolean isSettingUp() {
return this.isSettingUp;
}
public void setSettingUp(final boolean isSettingUp) {
this.isSettingUp = isSettingUp;
}
public boolean isCanHitSelf() {
return this.canHitSelf;
}
public void setCanHitSelf(final boolean canHitSelf) {
this.canHitSelf = canHitSelf;
}
public long getTime() {
return this.time;
}
public void setTime(final long time) {
this.time = time;
}
public long getInterval() {
return this.interval;
}
public void setInterval(final long interval) {
this.interval = interval;
}
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 double getPushFactor() {
return this.pushFactor;
}
public void setPushFactor(final double pushFactor) {
this.pushFactor = pushFactor;
}
public double getSelectRange() {
return this.selectRange;
}
public void setSelectRange(final double selectRange) {
this.selectRange = selectRange;
}
public double getDeflectRange() {
return this.deflectRange;
}
public void setDeflectRange(final double deflectRange) {
this.deflectRange = deflectRange;
}
public void setCollisionRadius(final double collisionRadius) {
this.collisionRadius = collisionRadius;
}
public Material getSourcetype() {
return this.sourceType;
}
public void setSourcetype(final Material sourcetype) {
this.sourceType = sourcetype;
}
public Location getDestination() {
return this.destination;
}
public void setDestination(final Location destination) {
this.destination = destination;
}
public Location getFirstDestination() {
return this.firstDestination;
}
public void setFirstDestination(final Location firstDestination) {
this.firstDestination = firstDestination;
}
public Block getSourceBlock() {
return this.sourceBlock;
}
public void setSourceBlock(final Block sourceBlock) {
this.sourceBlock = sourceBlock;
}
public void setCooldown(final long cooldown) {
this.cooldown = cooldown;
}
public void setLocation(final Location location) {
this.location = location;
}
}