diff --git a/pom.xml b/pom.xml index eace9821..413e2dae 100644 --- a/pom.xml +++ b/pom.xml @@ -74,6 +74,18 @@ commons-lang3 3.8.1 + + + com.zaxxer + HikariCP + 3.4.1 + + + + mysql + mysql-connector-java + 8.0.18 + me.markeh @@ -244,4 +256,4 @@ ${project.build.directory} UTF-8 - \ No newline at end of file + diff --git a/src/com/projectkorra/projectkorra/BendingManager.java b/src/com/projectkorra/projectkorra/BendingManager.java index e173b0f2..62e8b3ac 100644 --- a/src/com/projectkorra/projectkorra/BendingManager.java +++ b/src/com/projectkorra/projectkorra/BendingManager.java @@ -1,18 +1,12 @@ package com.projectkorra.projectkorra; -import java.util.HashMap; -import java.util.UUID; - -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.World; -import org.bukkit.entity.Player; - +import co.aikar.timings.lib.MCTiming; import com.projectkorra.projectkorra.ability.CoreAbility; import com.projectkorra.projectkorra.ability.ElementalAbility; import com.projectkorra.projectkorra.configuration.ConfigManager; import com.projectkorra.projectkorra.configuration.configs.properties.FirePropertiesConfig; import com.projectkorra.projectkorra.configuration.configs.properties.WaterPropertiesConfig; +import com.projectkorra.projectkorra.cooldown.CooldownManager; import com.projectkorra.projectkorra.earthbending.metal.MetalClips; import com.projectkorra.projectkorra.object.HorizontalVelocityTracker; import com.projectkorra.projectkorra.util.ActionBar; @@ -20,8 +14,13 @@ import com.projectkorra.projectkorra.util.RevertChecker; import com.projectkorra.projectkorra.util.TempArmor; import com.projectkorra.projectkorra.util.TempPotionEffect; import com.projectkorra.projectkorra.waterbending.blood.Bloodbending; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.World; +import org.bukkit.entity.Player; -import co.aikar.timings.lib.MCTiming; +import java.util.HashMap; +import java.util.UUID; public class BendingManager implements Runnable { @@ -51,6 +50,10 @@ public class BendingManager implements Runnable { return instance; } + /** + * @deprecated handled by {@link CooldownManager}. + */ + @Deprecated public void handleCooldowns() { for (final UUID uuid : BendingPlayer.getPlayers().keySet()) { final BendingPlayer bPlayer = BendingPlayer.getPlayers().get(uuid); diff --git a/src/com/projectkorra/projectkorra/BendingPlayer.java b/src/com/projectkorra/projectkorra/BendingPlayer.java index 66dd92cb..95a3351f 100644 --- a/src/com/projectkorra/projectkorra/BendingPlayer.java +++ b/src/com/projectkorra/projectkorra/BendingPlayer.java @@ -1,23 +1,5 @@ package com.projectkorra.projectkorra; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Stream; - -import org.apache.commons.lang3.builder.ToStringBuilder; -import org.apache.commons.lang3.builder.ToStringStyle; -import org.bukkit.Bukkit; -import org.bukkit.GameMode; -import org.bukkit.Location; -import org.bukkit.OfflinePlayer; -import org.bukkit.entity.Player; - import com.projectkorra.projectkorra.Element.SubElement; import com.projectkorra.projectkorra.ability.Ability; import com.projectkorra.projectkorra.ability.AvatarAbility; @@ -35,11 +17,31 @@ import com.projectkorra.projectkorra.storage.DBConnection; import com.projectkorra.projectkorra.util.Cooldown; import com.projectkorra.projectkorra.util.DBCooldownManager; import com.projectkorra.projectkorra.waterbending.blood.Bloodbending; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import org.bukkit.Bukkit; +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Stream; /** * Class that presents a player and stores all bending information about the * player. + * + * @deprecated use {@link com.projectkorra.projectkorra.player.BendingPlayer}. */ +@Deprecated @SuppressWarnings("rawtypes") public class BendingPlayer { diff --git a/src/com/projectkorra/projectkorra/GeneralMethods.java b/src/com/projectkorra/projectkorra/GeneralMethods.java index ee20c206..bc3f8f16 100644 --- a/src/com/projectkorra/projectkorra/GeneralMethods.java +++ b/src/com/projectkorra/projectkorra/GeneralMethods.java @@ -1,59 +1,8 @@ package com.projectkorra.projectkorra; -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.FileWriter; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Date; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; - -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.Color; -import org.bukkit.GameMode; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.World; -import org.bukkit.block.Block; -import org.bukkit.block.BlockFace; -import org.bukkit.block.BlockState; -import org.bukkit.block.data.BlockData; -import org.bukkit.block.data.Levelled; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Entity; -import org.bukkit.entity.FallingBlock; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Player; -import org.bukkit.entity.TNTPrimed; -import org.bukkit.inventory.ItemStack; -import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.PluginManager; -import org.bukkit.plugin.java.JavaPlugin; -import org.bukkit.scheduler.BukkitRunnable; -import org.bukkit.util.Vector; - +import br.net.fabiozumbi12.RedProtect.Bukkit.API.RedProtectAPI; +import br.net.fabiozumbi12.RedProtect.Bukkit.RedProtect; +import br.net.fabiozumbi12.RedProtect.Bukkit.Region; import com.bekvon.bukkit.residence.Residence; import com.bekvon.bukkit.residence.api.ResidenceInterface; import com.bekvon.bukkit.residence.protection.ClaimedResidence; @@ -102,6 +51,7 @@ import com.projectkorra.projectkorra.configuration.configs.properties.ChatProper import com.projectkorra.projectkorra.configuration.configs.properties.GeneralPropertiesConfig; import com.projectkorra.projectkorra.earthbending.EarthBlast; import com.projectkorra.projectkorra.earthbending.passive.EarthPassive; +import com.projectkorra.projectkorra.element.ElementManager; import com.projectkorra.projectkorra.event.BendingPlayerCreationEvent; import com.projectkorra.projectkorra.event.BendingReloadEvent; import com.projectkorra.projectkorra.event.PlayerBindChangeEvent; @@ -109,6 +59,7 @@ import com.projectkorra.projectkorra.firebending.FireBlast; import com.projectkorra.projectkorra.firebending.FireShield; import com.projectkorra.projectkorra.firebending.combustion.Combustion; import com.projectkorra.projectkorra.object.Preset; +import com.projectkorra.projectkorra.player.BendingPlayerManager; import com.projectkorra.projectkorra.storage.DBConnection; import com.projectkorra.projectkorra.util.ActionBar; import com.projectkorra.projectkorra.util.BlockCacheElement; @@ -131,10 +82,6 @@ import com.songoda.kingdoms.constants.land.Land; import com.songoda.kingdoms.constants.land.SimpleChunkLocation; import com.songoda.kingdoms.constants.player.KingdomPlayer; import com.songoda.kingdoms.manager.game.GameManagement; - -import br.net.fabiozumbi12.RedProtect.Bukkit.RedProtect; -import br.net.fabiozumbi12.RedProtect.Bukkit.Region; -import br.net.fabiozumbi12.RedProtect.Bukkit.API.RedProtectAPI; import me.markeh.factionsframework.entities.FPlayer; import me.markeh.factionsframework.entities.FPlayers; import me.markeh.factionsframework.entities.Faction; @@ -146,6 +93,60 @@ import net.md_5.bungee.api.chat.ClickEvent; import net.md_5.bungee.api.chat.ComponentBuilder; import net.md_5.bungee.api.chat.HoverEvent; import net.md_5.bungee.api.chat.TextComponent; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Color; +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.BlockState; +import org.bukkit.block.data.BlockData; +import org.bukkit.block.data.Levelled; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Entity; +import org.bukkit.entity.FallingBlock; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.entity.TNTPrimed; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.MainHand; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginManager; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.util.Vector; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; @SuppressWarnings("rawtypes") public class GeneralMethods { @@ -323,7 +324,10 @@ public class GeneralMethods { * @param uuid The UUID of the player * @param player The player name * @throws SQLException + * + * @deprecated use {@link BendingPlayerManager} and {@link ElementManager}. */ + @Deprecated public static void createBendingPlayer(final UUID uuid, final String player) { new BukkitRunnable() { @@ -335,6 +339,7 @@ public class GeneralMethods { }.runTaskAsynchronously(ProjectKorra.plugin); } + @Deprecated private static void createBendingPlayerAsynchronously(final UUID uuid, final String player) { ResultSet rs = DBConnection.sql.readQuery("SELECT * FROM pk_players WHERE uuid = '" + uuid.toString() + "'"); try { @@ -725,17 +730,17 @@ public class GeneralMethods { */ public static Entity getClosestEntity(Location center, double radius) { Entity found = null; - double distance = radius * radius; + Double distance = null; for (Entity entity : GeneralMethods.getEntitiesAroundPoint(center, radius)) { - double check = center.distance(entity.getLocation()); + double check = center.distanceSquared(entity.getLocation()); - if (check < distance) { + if (distance == null || check < distance) { found = entity; distance = check; } } - + return found; } @@ -747,12 +752,12 @@ public class GeneralMethods { */ public static LivingEntity getClosestLivingEntity(Location center, double radius) { LivingEntity le = null; - double distance = radius * radius; + Double distance = null; for (Entity entity : GeneralMethods.getEntitiesAroundPoint(center, radius)) { - double check = center.distance(entity.getLocation()); + double check = center.distanceSquared(entity.getLocation()); - if (entity instanceof LivingEntity && check < distance) { + if (entity instanceof LivingEntity && (distance == null || check < distance)) { le = (LivingEntity) entity; distance = check; } @@ -962,6 +967,16 @@ public class GeneralMethods { return location.clone().subtract(new Vector(Math.cos(angle), 0, Math.sin(angle)).normalize().multiply(distance)); } + public static Location getMainHandLocation(final Player player) { + Location loc; + if (player.getMainHand() == MainHand.LEFT) { + loc = GeneralMethods.getLeftSide(player.getLocation(), .55).add(0, 1.2, 0); + } else { + loc = GeneralMethods.getRightSide(player.getLocation(), .55).add(0, 1.2, 0); + } + return loc; + } + public static Plugin getProbending() { if (hasProbending()) { return Bukkit.getServer().getPluginManager().getPlugin("Probending"); @@ -1031,8 +1046,8 @@ public class GeneralMethods { public static Entity getTargetedEntity(final Player player, final double range) { return getTargetedEntity(player, range, new ArrayList()); } - - public static Location getTargetedLocation(final Player player, final double range, final boolean ignoreTempBlocks, final Material... nonOpaque2) { + + public static Location getTargetedLocation(final Player player, final double range, final boolean ignoreTempBlocks, final boolean checkDiagonals, final Material... nonOpaque2) { final Location origin = player.getEyeLocation(); final Vector direction = origin.getDirection(); @@ -1052,6 +1067,11 @@ public class GeneralMethods { for (double i = 0; i < range; i += 0.2) { location.add(vec); + + if (checkDiagonals && checkDiagonalWall(location, vec)) { + location.subtract(vec); + break; + } final Block block = location.getBlock(); @@ -1068,12 +1088,16 @@ public class GeneralMethods { return location; } + public static Location getTargetedLocation(final Player player, final double range, final boolean ignoreTempBlocks, final Material... nonOpaque2) { + return getTargetedLocation(player, range, ignoreTempBlocks, true, nonOpaque2); + } + public static Location getTargetedLocation(final Player player, final double range, final Material... nonOpaque2) { return getTargetedLocation(player, range, false, nonOpaque2); } public static Location getTargetedLocation(final Player player, final int range) { - return getTargetedLocation(player, range, Material.AIR); + return getTargetedLocation(player, range, false); } public static Block getTopBlock(final Location loc, final int range) { diff --git a/src/com/projectkorra/projectkorra/ProjectKorra.java b/src/com/projectkorra/projectkorra/ProjectKorra.java index 67fc14f0..ba301678 100644 --- a/src/com/projectkorra/projectkorra/ProjectKorra.java +++ b/src/com/projectkorra/projectkorra/ProjectKorra.java @@ -1,17 +1,7 @@ package com.projectkorra.projectkorra; -import java.lang.reflect.Method; -import java.util.Base64; -import java.util.HashMap; -import java.util.logging.Logger; - -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.Statistic; -import org.bukkit.entity.Player; -import org.bukkit.plugin.java.JavaPlugin; -import org.bukkit.scheduler.BukkitTask; - +import co.aikar.timings.lib.MCTiming; +import co.aikar.timings.lib.TimingManager; import com.bekvon.bukkit.residence.protection.FlagPermissions; import com.projectkorra.projectkorra.ability.CoreAbility; import com.projectkorra.projectkorra.ability.util.CollisionInitializer; @@ -28,6 +18,7 @@ import com.projectkorra.projectkorra.earthbending.util.EarthbendingManager; import com.projectkorra.projectkorra.firebending.util.FirebendingManager; import com.projectkorra.projectkorra.hooks.PlaceholderAPIHook; import com.projectkorra.projectkorra.hooks.WorldGuardFlag; +import com.projectkorra.projectkorra.module.ModuleManager; import com.projectkorra.projectkorra.object.Preset; import com.projectkorra.projectkorra.storage.DBConnection; import com.projectkorra.projectkorra.util.Metrics; @@ -36,9 +27,17 @@ import com.projectkorra.projectkorra.util.StatisticsManager; import com.projectkorra.projectkorra.util.TempBlock; import com.projectkorra.projectkorra.util.Updater; import com.projectkorra.projectkorra.waterbending.util.WaterbendingManager; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Statistic; +import org.bukkit.entity.Player; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.scheduler.BukkitTask; -import co.aikar.timings.lib.MCTiming; -import co.aikar.timings.lib.TimingManager; +import java.lang.reflect.Method; +import java.util.Base64; +import java.util.HashMap; +import java.util.logging.Logger; public class ProjectKorra extends JavaPlugin { @@ -89,6 +88,7 @@ public class ProjectKorra extends JavaPlugin { } Manager.startup(); + ModuleManager.startup(); this.getServer().getPluginManager().registerEvents(new PKListener(this), this); this.getServer().getScheduler().scheduleSyncRepeatingTask(this, new BendingManager(), 0, 1); diff --git a/src/com/projectkorra/projectkorra/ability/AbilityManager.java b/src/com/projectkorra/projectkorra/ability/AbilityManager.java new file mode 100644 index 00000000..4e43601c --- /dev/null +++ b/src/com/projectkorra/projectkorra/ability/AbilityManager.java @@ -0,0 +1,129 @@ +package com.projectkorra.projectkorra.ability; + +import com.projectkorra.projectkorra.GeneralMethods; +import com.projectkorra.projectkorra.ability.util.MultiAbilityManager; +import com.projectkorra.projectkorra.firebending.FireBlast; +import com.projectkorra.projectkorra.module.DatabaseModule; +import com.projectkorra.projectkorra.module.ModuleManager; +import com.projectkorra.projectkorra.player.BendingPlayer; +import com.projectkorra.projectkorra.player.BendingPlayerLoadedEvent; +import com.projectkorra.projectkorra.player.BendingPlayerManager; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; + +import java.sql.SQLException; + +public class AbilityManager extends DatabaseModule { + + private final BendingPlayerManager bendingPlayerManager; + + private AbilityManager() { + super("Ability", new AbilityRepository()); + + this.bendingPlayerManager = ModuleManager.getModule(BendingPlayerManager.class); + + runAsync(() -> { + try { + getRepository().createTables(); + } catch (SQLException e) { + e.printStackTrace(); + } + + runSync(() -> { + log("Created database tables."); + }); + }); + + registerAbilities(); + } + + private void registerAbilities() { + registerAbility(FireBlast.class); + } + + private void registerAbility(Class abilityClass) { + // TODO + } + + @EventHandler + public void onBendingPlayerLoaded(BendingPlayerLoadedEvent event) { + BendingPlayer bendingPlayer = event.getBendingPlayer(); + + runAsync(() -> { + try { + String[] abilities = getRepository().selectPlayerAbilities(bendingPlayer.getId()); + + bendingPlayer.setAbilities(abilities); + } catch (SQLException e) { + e.printStackTrace(); + } + }); + } + + public boolean bindAbility(Player player, String abilityName, int slot) { + PlayerBindAbilityEvent playerBindAbilityEvent = new PlayerBindAbilityEvent(player, abilityName); + getPlugin().getServer().getPluginManager().callEvent(playerBindAbilityEvent); + + if (playerBindAbilityEvent.isCancelled()) { + String cancelMessage = playerBindAbilityEvent.getCancelMessage(); + + if (cancelMessage != null) { + GeneralMethods.sendBrandingMessage(player, cancelMessage); + } + + return false; + } + + BendingPlayer bendingPlayer = this.bendingPlayerManager.getBendingPlayer(player); + + bendingPlayer.setAbility(slot, abilityName); + + runAsync(() -> { + try { + getRepository().insertPlayerAbility(bendingPlayer.getId(), abilityName, slot); + } catch (SQLException e) { + e.printStackTrace(); + } + }); + + return true; + } + + public boolean unbindAbility(Player player, int slot) { + BendingPlayer bendingPlayer = this.bendingPlayerManager.getBendingPlayer(player); + + String abilityName = bendingPlayer.getAbility(slot); + + if (abilityName == null) { + player.sendMessage("No ability bound"); + return false; + } + + bendingPlayer.setAbility(slot, null); + + runAsync(() -> { + try { + getRepository().deletePlayerAbility(bendingPlayer.getId(), abilityName); + } catch (SQLException e) { + e.printStackTrace(); + } + }); + + return true; + } + + public void clearBinds(Player player) { + BendingPlayer bendingPlayer = this.bendingPlayerManager.getBendingPlayer(player); + + bendingPlayer.setAbilities(new String[9]); + + runAsync(() -> { + try { + getRepository().deletePlayerAbilities(bendingPlayer.getId()); + } catch (SQLException e) { + e.printStackTrace(); + } + }); + } +} diff --git a/src/com/projectkorra/projectkorra/ability/AbilityRepository.java b/src/com/projectkorra/projectkorra/ability/AbilityRepository.java new file mode 100644 index 00000000..ad4ca5b2 --- /dev/null +++ b/src/com/projectkorra/projectkorra/ability/AbilityRepository.java @@ -0,0 +1,100 @@ +package com.projectkorra.projectkorra.ability; + +import com.projectkorra.projectkorra.database.DatabaseQuery; +import com.projectkorra.projectkorra.database.DatabaseRepository; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +public class AbilityRepository extends DatabaseRepository { + + private static final DatabaseQuery CREATE_TABLE_PLAYER_ABILITIES = DatabaseQuery.newBuilder() + .mysql("CREATE TABLE IF NOT EXISTS pk_player_abilities (player_id INTEGER REFERENCES pk_bending_players (player_id), ability_name VARCHAR(50) NOT NULL, slot TINYINT NOT NULL, PRIMARY KEY (player_id, ability_name), INDEX player_index (player_id), INDEX ability_index (ability_name));") + .sqlite("CREATE TABLE IF NOT EXISTS pk_player_abilities (player_id INTEGER REFERENCES pk_bending_players (player_id), ability_name VARCHAR(50) NOT NULL, slot TINYINT NOT NULL, PRIMARY KEY (player_id, ability_name)); CREATE INDEX player_index ON pk_player_abilities (player_id); CREATE INDEX ability_index ON pk_player_abilities (ability_name);") + .build(); + + private static final DatabaseQuery SELECT_PLAYER_ABILITIES = DatabaseQuery.newBuilder() + .query("SELECT ability_name, slot FROM pk_player_abilities WHERE player_id = ?;") + .build(); + + private static final DatabaseQuery INSERT_PLAYER_ABILITY = DatabaseQuery.newBuilder() + .query("INSERT INTO pk_player_abilities VALUES (?, ?, ?);") + .build(); + + private static final DatabaseQuery DELETE_PLAYER_ABILITIES = DatabaseQuery.newBuilder() + .query("DELETE FROM pk_player_abilities WHERE player_id = ?") + .build(); + + private static final DatabaseQuery DELETE_PLAYER_ABILITY = DatabaseQuery.newBuilder() + .query("DELETE FROM pk_player_abilities WHERE player_id = ? AND ability_name = ?;") + .build(); + + protected void createTables() throws SQLException { + Connection connection = getDatabase().getConnection(); + + try (PreparedStatement statement = connection.prepareStatement(CREATE_TABLE_PLAYER_ABILITIES.getQuery())) { + statement.executeUpdate(); + } + } + + protected String[] selectPlayerAbilities(int playerId) throws SQLException { + Connection connection = getDatabase().getConnection(); + + try (PreparedStatement statement = connection.prepareStatement(SELECT_PLAYER_ABILITIES.getQuery())) { + statement.setInt(1, playerId); + + String[] abilities = new String[9]; + + try (ResultSet rs = statement.executeQuery()) { + while (rs.next()) { + String abilityName = rs.getString("ability_name"); + int slot = rs.getInt("slot"); + + if (slot < 0 || slot >= abilities.length) { + // TODO Log illegal slot + continue; + } + + abilities[slot] = abilityName; + } + + return abilities; + } + } + } + + protected void insertPlayerAbility(int playerId, String abilityName, int slot) throws SQLException { + Connection connection = getDatabase().getConnection(); + + try (PreparedStatement statement = connection.prepareStatement(INSERT_PLAYER_ABILITY.getQuery())) { + statement.setInt(1, playerId); + statement.setString(2, abilityName); + statement.setInt(3, slot); + + statement.executeUpdate(); + } + } + + protected void deletePlayerAbilities(int playerId) throws SQLException { + Connection connection = getDatabase().getConnection(); + + try (PreparedStatement statement = connection.prepareStatement(DELETE_PLAYER_ABILITIES.getQuery())) { + statement.setInt(1, playerId); + + statement.executeUpdate(); + } + } + + protected void deletePlayerAbility(int playerId, String abilityName) throws SQLException { + Connection connection = getDatabase().getConnection(); + + try (PreparedStatement statement = connection.prepareStatement(DELETE_PLAYER_ABILITY.getQuery())) { + statement.setInt(1, playerId); + statement.setString(2, abilityName); + + statement.executeUpdate(); + } + } +} diff --git a/src/com/projectkorra/projectkorra/ability/EarthAbility.java b/src/com/projectkorra/projectkorra/ability/EarthAbility.java index d0aae2fc..1617ebaf 100644 --- a/src/com/projectkorra/projectkorra/ability/EarthAbility.java +++ b/src/com/projectkorra/projectkorra/ability/EarthAbility.java @@ -563,10 +563,10 @@ public abstract class EarthAbility extends ElementalAbi if (block.equals(sourceblock)) { info.getState().update(true, false); if (RaiseEarth.blockInAllAffectedBlocks(sourceblock)) { - EarthAbility.revertBlock(sourceblock); + RaiseEarth.revertAffectedBlock(sourceblock); } if (RaiseEarth.blockInAllAffectedBlocks(block)) { - EarthAbility.revertBlock(block); + RaiseEarth.revertAffectedBlock(block); } MOVED_EARTH.remove(block); return true; @@ -595,10 +595,10 @@ public abstract class EarthAbility extends ElementalAbi } if (RaiseEarth.blockInAllAffectedBlocks(sourceblock)) { - EarthAbility.revertBlock(sourceblock); + RaiseEarth.revertAffectedBlock(sourceblock); } if (RaiseEarth.blockInAllAffectedBlocks(block)) { - EarthAbility.revertBlock(block); + RaiseEarth.revertAffectedBlock(block); } MOVED_EARTH.remove(block); } diff --git a/src/com/projectkorra/projectkorra/ability/PlayerBindAbilityEvent.java b/src/com/projectkorra/projectkorra/ability/PlayerBindAbilityEvent.java new file mode 100644 index 00000000..0317ee46 --- /dev/null +++ b/src/com/projectkorra/projectkorra/ability/PlayerBindAbilityEvent.java @@ -0,0 +1,61 @@ +package com.projectkorra.projectkorra.ability; + +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; + +public class PlayerBindAbilityEvent extends PlayerEvent implements Cancellable +{ + private static final HandlerList HANDLER_LIST = new HandlerList(); + + private final String abilityName; + + private boolean cancelled; + private String cancelMessage; + + public PlayerBindAbilityEvent(Player player, String abilityName) + { + super(player); + + this.abilityName = abilityName; + } + + public String getAbilityName() + { + return this.abilityName; + } + + @Override + public boolean isCancelled() + { + return cancelled; + } + + @Override + public void setCancelled(boolean cancelled) + { + this.cancelled = cancelled; + } + + public String getCancelMessage() + { + return this.cancelMessage; + } + + public void setCancelMessage(String cancelMessage) + { + this.cancelMessage = cancelMessage; + } + + @Override + public HandlerList getHandlers() + { + return HANDLER_LIST; + } + + public static HandlerList getHandlerList() + { + return HANDLER_LIST; + } +} diff --git a/src/com/projectkorra/projectkorra/ability/util/MultiAbilityManager.java b/src/com/projectkorra/projectkorra/ability/util/MultiAbilityManager.java index 1317c01c..38943ff7 100644 --- a/src/com/projectkorra/projectkorra/ability/util/MultiAbilityManager.java +++ b/src/com/projectkorra/projectkorra/ability/util/MultiAbilityManager.java @@ -6,9 +6,12 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import com.projectkorra.projectkorra.ability.PlayerBindAbilityEvent; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; import org.bukkit.scheduler.BukkitRunnable; import com.projectkorra.projectkorra.BendingPlayer; @@ -17,7 +20,7 @@ import com.projectkorra.projectkorra.GeneralMethods; import com.projectkorra.projectkorra.ProjectKorra; import com.projectkorra.projectkorra.event.PlayerBindChangeEvent; -public class MultiAbilityManager { +public class MultiAbilityManager implements Listener { public static Map playerAbilities = new ConcurrentHashMap<>(); public static Map playerSlot = new ConcurrentHashMap<>(); @@ -34,6 +37,19 @@ public class MultiAbilityManager { waterArms.add(new MultiAbilityInfoSub("Spear", Element.ICE)); multiAbilityList.add(new MultiAbilityInfo("WaterArms", waterArms)); manage(); + + // TODO Properly set this up as a Module + ProjectKorra.plugin.getServer().getPluginManager().registerEvents(this, ProjectKorra.plugin); + } + + @EventHandler + public void onPlayerBindAbility(PlayerBindAbilityEvent event) + { + if (playerAbilities.containsKey(event.getPlayer())) + { + event.setCancelled(true); + event.setCancelMessage(ChatColor.RED + "You can't edit your binds right now!"); + } } /** diff --git a/src/com/projectkorra/projectkorra/airbending/AirBlast.java b/src/com/projectkorra/projectkorra/airbending/AirBlast.java index 1b1c0650..13ff8fca 100644 --- a/src/com/projectkorra/projectkorra/airbending/AirBlast.java +++ b/src/com/projectkorra/projectkorra/airbending/AirBlast.java @@ -213,72 +213,72 @@ public class AirBlast extends AirAbility { } private void affect(final Entity entity) { - final boolean isUser = entity.getUniqueId() == this.player.getUniqueId(); - - if (!isUser || this.isFromOtherOrigin) { - this.pushFactor = this.pushFactorForOthers; - final Vector velocity = entity.getVelocity(); - final double max = this.speed / this.speedFactor; - double factor = this.pushFactor; - - final Vector push = this.direction.clone(); - if (Math.abs(push.getY()) > max && !isUser) { - if (push.getY() < 0) { - push.setY(-max); - } else { - push.setY(max); - } - } - if (this.location.getWorld().equals(this.origin.getWorld())) { - factor *= 1 - this.location.distance(this.origin) / (2 * this.range); - } - - if (isUser && GeneralMethods.isSolid(this.player.getLocation().add(0, -.5, 0).getBlock())) { - factor *= .5; - } - - final double comp = velocity.dot(push.clone().normalize()); - if (comp > factor) { - velocity.multiply(.5); - velocity.add(push.clone().normalize().multiply(velocity.clone().dot(push.clone().normalize()))); - } else if (comp + factor * .5 > factor) { - velocity.add(push.clone().multiply(factor - comp)); - } else { - velocity.add(push.clone().multiply(factor * .5)); - } - - if (entity instanceof Player) { - if (Commands.invincible.contains(((Player) entity).getName())) { - return; - } - } - - if (Double.isNaN(velocity.length())) { + if (entity instanceof Player) { + if (Commands.invincible.contains(((Player) entity).getName())) { return; } + } + + final boolean isUser = entity.getUniqueId() == this.player.getUniqueId(); + double knockback = this.pushFactorForOthers; - GeneralMethods.setVelocity(entity, velocity); - if (this.source != null) { - new HorizontalVelocityTracker(entity, this.player, 200l, this.source); + if (isUser) { + if (isFromOtherOrigin) { + knockback = this.pushFactor; } else { - new HorizontalVelocityTracker(entity, this.player, 200l, this); - } - - if (entity.getFireTicks() > 0) { - entity.getWorld().playEffect(entity.getLocation(), Effect.EXTINGUISH, 0); - } - - entity.setFireTicks(0); - breakBreathbendingHold(entity); - - if (this.source != null && (this.damage > 0 && entity instanceof LivingEntity && !entity.equals(this.player) && !this.affectedEntities.contains(entity))) { - DamageHandler.damageEntity(entity, this.damage, this.source); - this.affectedEntities.add(entity); - } else if (this.source == null && (this.damage > 0 && entity instanceof LivingEntity && !entity.equals(this.player) && !this.affectedEntities.contains(entity))) { - DamageHandler.damageEntity(entity, this.damage, this); - this.affectedEntities.add(entity); + return; } } + + final double max = this.speed / this.speedFactor; + + final Vector push = this.direction.clone(); + if (Math.abs(push.getY()) > max && !isUser) { + if (push.getY() < 0) { + push.setY(-max); + } else { + push.setY(max); + } + } + + if (this.location.getWorld().equals(this.origin.getWorld())) { + knockback *= 1 - this.location.distance(this.origin) / (2 * this.range); + } + + if (GeneralMethods.isSolid(entity.getLocation().add(0, -0.5, 0).getBlock()) && source == null) { + knockback *= 0.85; + } + + push.normalize().multiply(knockback); + + if (Math.abs(entity.getVelocity().dot(push)) > knockback && entity.getVelocity().angle(push) > Math.PI / 3) { + push.normalize().add(entity.getVelocity()).multiply(knockback); + } + + GeneralMethods.setVelocity(entity, push); + + if (this.source != null) { + new HorizontalVelocityTracker(entity, this.player, 200l, this.source); + } else { + new HorizontalVelocityTracker(entity, this.player, 200l, this); + } + + if (this.damage > 0 && entity instanceof LivingEntity && !entity.equals(this.player) && !this.affectedEntities.contains(entity)) { + if (this.source != null) { + DamageHandler.damageEntity(entity, this.damage, this.source); + } else { + DamageHandler.damageEntity(entity, this.damage, this); + } + + this.affectedEntities.add(entity); + } + + if (entity.getFireTicks() > 0) { + entity.getWorld().playEffect(entity.getLocation(), Effect.EXTINGUISH, 0); + } + + entity.setFireTicks(0); + breakBreathbendingHold(entity); } @Override diff --git a/src/com/projectkorra/projectkorra/airbending/AirBurst.java b/src/com/projectkorra/projectkorra/airbending/AirBurst.java index 6ea1c3ff..31832346 100644 --- a/src/com/projectkorra/projectkorra/airbending/AirBurst.java +++ b/src/com/projectkorra/projectkorra/airbending/AirBurst.java @@ -325,10 +325,6 @@ public class AirBurst extends AirAbility { return this.blasts; } - public ArrayList getAffectedEntities() { - return this.affectedEntities; - } - @Override public Class getConfigType() { return AirBurstConfig.class; diff --git a/src/com/projectkorra/projectkorra/airbending/AirSuction.java b/src/com/projectkorra/projectkorra/airbending/AirSuction.java index 175715f4..1a1d40f1 100644 --- a/src/com/projectkorra/projectkorra/airbending/AirSuction.java +++ b/src/com/projectkorra/projectkorra/airbending/AirSuction.java @@ -44,6 +44,8 @@ public class AirSuction extends AirAbility { private double radius; @Attribute(Attribute.KNOCKBACK) private double pushFactor; + @Attribute(Attribute.KNOCKBACK + "Others") + private double pushFactorForOthers; private Random random; private Location location; private Location origin; @@ -78,7 +80,8 @@ public class AirSuction extends AirAbility { this.speed = config.Speed; this.range = config.Range; this.radius = config.Radius; - this.pushFactor = config.PushFactor; + this.pushFactor = config.PushFactor_Self; + this.pushFactorForOthers = config.PushFactor_Others; this.cooldown = config.Cooldown; this.random = new Random(); this.origin = this.getTargetLocation(); @@ -203,10 +206,15 @@ public class AirSuction extends AirAbility { if ((entity.getEntityId() == this.player.getEntityId()) && !this.canAffectSelf) { continue; } - final Vector velocity = entity.getVelocity(); + + double knockback = this.pushFactor; + + if (entity.getEntityId() != player.getEntityId()) { + knockback = this.pushFactorForOthers; + } + final double max = this.speed; final Vector push = this.direction.clone(); - double factor = this.pushFactor; if (Math.abs(push.getY()) > max) { if (push.getY() < 0) { @@ -217,20 +225,16 @@ public class AirSuction extends AirAbility { } if (this.location.getWorld().equals(this.origin.getWorld())) { - factor *= 1 - this.location.distance(this.origin) / (2 * this.range); + knockback *= 1 - this.location.distance(this.origin) / (2 * this.range); + } + + push.normalize().multiply(knockback); + + if (Math.abs(entity.getVelocity().dot(push)) > knockback) { + push.normalize().add(entity.getVelocity()).multiply(knockback); } - final double comp = velocity.dot(push.clone().normalize()); - if (comp > factor) { - velocity.multiply(.5); - velocity.add(push.clone().normalize().multiply(velocity.clone().dot(push.clone().normalize()))); - } else if (comp + factor * .5 > factor) { - velocity.add(push.clone().multiply(factor - comp)); - } else { - velocity.add(push.clone().multiply(factor * .5)); - } - - GeneralMethods.setVelocity(entity, velocity); + GeneralMethods.setVelocity(entity, push.normalize().multiply(knockback)); new HorizontalVelocityTracker(entity, this.player, 200l, this); entity.setFallDistance(0); diff --git a/src/com/projectkorra/projectkorra/airbending/AirSwipe.java b/src/com/projectkorra/projectkorra/airbending/AirSwipe.java index eabf8215..c10a88fb 100644 --- a/src/com/projectkorra/projectkorra/airbending/AirSwipe.java +++ b/src/com/projectkorra/projectkorra/airbending/AirSwipe.java @@ -78,7 +78,7 @@ public class AirSwipe extends AirAbility { } this.charging = charging; - this.origin = player.getEyeLocation(); + this.origin = GeneralMethods.getMainHandLocation(player); this.particles = config.AnimationParticleAmount; this.arc = config.Arc; this.arcIncrement = config.StepSize; diff --git a/src/com/projectkorra/projectkorra/airbending/Suffocate.java b/src/com/projectkorra/projectkorra/airbending/Suffocate.java index 776b4fcf..04cdbe6e 100644 --- a/src/com/projectkorra/projectkorra/airbending/Suffocate.java +++ b/src/com/projectkorra/projectkorra/airbending/Suffocate.java @@ -143,7 +143,6 @@ public class Suffocate extends AirAbility { } } - this.bPlayer.addCooldown(this); this.start(); } @@ -172,7 +171,7 @@ public class Suffocate extends AirAbility { if (this.player.getWorld().equals(this.targets.get(0).getWorld())) { dist = this.player.getEyeLocation().distance(this.targets.get(0).getEyeLocation()); } - final Location targetLoc = this.player.getEyeLocation().clone().add(this.player.getEyeLocation().getDirection().normalize().multiply(dist)); + final Location targetLoc = GeneralMethods.getTargetedLocation(player, dist, false, getTransparentMaterials()); final List ents = GeneralMethods.getEntitiesAroundPoint(targetLoc, this.constantAimRadius); for (int i = 0; i < this.targets.size(); i++) { @@ -337,6 +336,7 @@ public class Suffocate extends AirAbility { @Override public void remove() { super.remove(); + this.bPlayer.addCooldown(this); for (int i = 0; i < this.tasks.size(); i++) { this.tasks.get(i).cancel(); this.tasks.remove(i); diff --git a/src/com/projectkorra/projectkorra/airbending/combo/AirSweep.java b/src/com/projectkorra/projectkorra/airbending/combo/AirSweep.java index b287ee0a..29f9e70d 100644 --- a/src/com/projectkorra/projectkorra/airbending/combo/AirSweep.java +++ b/src/com/projectkorra/projectkorra/airbending/combo/AirSweep.java @@ -3,6 +3,7 @@ package com.projectkorra.projectkorra.airbending.combo; import java.util.ArrayList; import java.util.List; +import com.projectkorra.projectkorra.object.HorizontalVelocityTracker; import org.bukkit.Location; import org.bukkit.entity.Entity; import org.bukkit.entity.LivingEntity; @@ -133,22 +134,23 @@ public class AirSweep extends AirAbility implements ComboAbility if (this.origin == null) { this.direction = this.player.getEyeLocation().getDirection().normalize(); - this.origin = this.player.getLocation().add(this.direction.clone().multiply(10)); + this.origin = GeneralMethods.getMainHandLocation(player).add(this.direction.clone().multiply(10)); } if (this.progressCounter < 8) { return; } if (this.destination == null) { - this.destination = this.player.getLocation().add(this.player.getEyeLocation().getDirection().normalize().multiply(10)); + this.destination = GeneralMethods.getMainHandLocation(player).add(GeneralMethods.getMainHandLocation(player).getDirection().normalize().multiply(10)); final Vector origToDest = GeneralMethods.getDirection(this.origin, this.destination); + final Location hand = GeneralMethods.getMainHandLocation(player); for (double i = 0; i < 30; i++) { final Location endLoc = this.origin.clone().add(origToDest.clone().multiply(i / 30)); - if (GeneralMethods.locationEqualsIgnoreDirection(this.player.getLocation(), endLoc)) { + if (GeneralMethods.locationEqualsIgnoreDirection(hand, endLoc)) { continue; } - final Vector vec = GeneralMethods.getDirection(this.player.getLocation(), endLoc); + final Vector vec = GeneralMethods.getDirection(hand, endLoc); - final FireComboStream fs = new FireComboStream(this.player, this, vec, this.player.getLocation(), this.range, this.speed); + final FireComboStream fs = new FireComboStream(this.player, this, vec, hand, this.range, this.speed); fs.setDensity(1); fs.setSpread(0F); fs.setUseNewParticles(true); @@ -193,17 +195,17 @@ public class AirSweep extends AirAbility implements ComboAbility this.remove(); return; } - if (!entity.equals(this.player) && !this.affectedEntities.contains(entity) && !(entity instanceof Player && Commands.invincible.contains(((Player) entity).getName()))) { - this.affectedEntities.add(entity); + if (!entity.equals(this.player) && !(entity instanceof Player && Commands.invincible.contains(((Player) entity).getName()))) { if (this.knockback != 0) { - final Vector force = fstream.getDirection(); - entity.setVelocity(force.multiply(this.knockback)); + final Vector force = fstream.getLocation().getDirection(); + GeneralMethods.setVelocity(entity, force.clone().multiply(this.knockback)); + new HorizontalVelocityTracker(entity, this.player, 200l, this); + entity.setFallDistance(0); } - if (this.damage != 0) { - if (entity instanceof LivingEntity) { - if (fstream.getAbility().getName().equalsIgnoreCase("AirSweep")) { - DamageHandler.damageEntity(entity, this.damage, this); - } else { + if(!this.affectedEntities.contains(entity)) { + this.affectedEntities.add(entity); + if (this.damage != 0) { + if (entity instanceof LivingEntity) { DamageHandler.damageEntity(entity, this.damage, this); } } diff --git a/src/com/projectkorra/projectkorra/command/PresetCommand.java b/src/com/projectkorra/projectkorra/command/PresetCommand.java index 9eac68ea..9da29fbf 100644 --- a/src/com/projectkorra/projectkorra/command/PresetCommand.java +++ b/src/com/projectkorra/projectkorra/command/PresetCommand.java @@ -78,41 +78,45 @@ public class PresetCommand extends PKCommand { bPlayer = BendingPlayer.getBendingPlayer(player); } - // bending preset list. - if (args.size() == 1) { - if (Arrays.asList(listaliases).contains(args.get(0)) && this.hasPermission(sender, "list")) { - boolean firstMessage = true; - - final List presets = Preset.presets.get(player.getUniqueId()); - final List presetNames = new ArrayList(); - - if (presets == null || presets.isEmpty()) { - GeneralMethods.sendBrandingMessage(sender, ChatColor.RED + this.noPresets); - return; - } - - for (final Preset preset : presets) { - presetNames.add(preset.getName()); - } - - for (final String s : this.getPage(presetNames, ChatColor.GOLD + "Presets: ", 1, false)) { - if (firstMessage) { - GeneralMethods.sendBrandingMessage(sender, s); - firstMessage = false; - } else { - sender.sendMessage(ChatColor.YELLOW + s); - } - } - - return; + int page = 1; + String name = null; + if (args.size() == 1 && !Arrays.asList(listaliases).contains(args.get(0))){ + this.help(sender, false); + } else if (args.size() >= 2) { + if (Arrays.asList(listaliases).contains(args.get(0))) { + page = Integer.parseInt(args.get(1)); } else { - this.help(sender, false); - return; + name = args.get(1); } } - final String name = args.get(1); - if (Arrays.asList(deletealiases).contains(args.get(0)) && this.hasPermission(sender, "delete")) { // bending preset delete name. + // bending preset list. + if (Arrays.asList(listaliases).contains(args.get(0)) && this.hasPermission(sender, "list")) { + boolean firstMessage = true; + + final List presets = Preset.presets.get(player.getUniqueId()); + final List presetNames = new ArrayList(); + + if (presets == null || presets.isEmpty()) { + GeneralMethods.sendBrandingMessage(sender, ChatColor.RED + this.noPresets); + return; + } + + for (final Preset preset : presets) { + presetNames.add(preset.getName()); + } + + for (final String s : this.getPage(presetNames, ChatColor.GOLD + "Presets: ", page, false)) { + if (firstMessage) { + GeneralMethods.sendBrandingMessage(sender, s); + firstMessage = false; + } else { + sender.sendMessage(ChatColor.YELLOW + s); + } + } + + return; + } else if (Arrays.asList(deletealiases).contains(args.get(0)) && this.hasPermission(sender, "delete")) { // bending preset delete name. if (!Preset.presetExists(player, name)) { GeneralMethods.sendBrandingMessage(sender, ChatColor.RED + this.noPresetName); return; diff --git a/src/com/projectkorra/projectkorra/configuration/ConfigManager.java b/src/com/projectkorra/projectkorra/configuration/ConfigManager.java index 95deda30..dd9d00ce 100644 --- a/src/com/projectkorra/projectkorra/configuration/ConfigManager.java +++ b/src/com/projectkorra/projectkorra/configuration/ConfigManager.java @@ -91,4 +91,4 @@ public class ConfigManager { return null; } } -} \ No newline at end of file +} diff --git a/src/com/projectkorra/projectkorra/configuration/configs/abilities/air/AirBlastConfig.java b/src/com/projectkorra/projectkorra/configuration/configs/abilities/air/AirBlastConfig.java index a0e33e7b..c00109c6 100644 --- a/src/com/projectkorra/projectkorra/configuration/configs/abilities/air/AirBlastConfig.java +++ b/src/com/projectkorra/projectkorra/configuration/configs/abilities/air/AirBlastConfig.java @@ -7,8 +7,8 @@ public class AirBlastConfig extends AbilityConfig { public final long Cooldown = 500; public final int AnimationParticleAmount = 5; public final int SelectionParticleAmount = 5; - public final double PushFactor_Self = 2.5; - public final double PushFactor_Others = 2.5; + public final double PushFactor_Self = 2.0; + public final double PushFactor_Others = 1.6; public final double Speed = 25; public final double Range = 20; public final double SelectionRange = 10; @@ -35,4 +35,4 @@ public class AirBlastConfig extends AbilityConfig { return new String[] { "Abilities", "Air" }; } -} \ No newline at end of file +} diff --git a/src/com/projectkorra/projectkorra/configuration/configs/abilities/air/AirBurstConfig.java b/src/com/projectkorra/projectkorra/configuration/configs/abilities/air/AirBurstConfig.java index 500ff7d8..49bc727d 100644 --- a/src/com/projectkorra/projectkorra/configuration/configs/abilities/air/AirBurstConfig.java +++ b/src/com/projectkorra/projectkorra/configuration/configs/abilities/air/AirBurstConfig.java @@ -7,7 +7,7 @@ public class AirBurstConfig extends AbilityConfig { public final long Cooldown = 0; public final long ChargeTime = 1750; public final double FallHeightThreshold = 15; - public final double PushFactor = 2.0; + public final double PushFactor = 2.8; public final double Damage = 0; public final double AnglePhi = 10; public final double AngleTheta = 10; @@ -31,4 +31,4 @@ public class AirBurstConfig extends AbilityConfig { return new String[] { "Abilities", "Air" }; } -} \ No newline at end of file +} diff --git a/src/com/projectkorra/projectkorra/configuration/configs/abilities/air/AirSuctionConfig.java b/src/com/projectkorra/projectkorra/configuration/configs/abilities/air/AirSuctionConfig.java index 1a0851f7..94f8c7ab 100644 --- a/src/com/projectkorra/projectkorra/configuration/configs/abilities/air/AirSuctionConfig.java +++ b/src/com/projectkorra/projectkorra/configuration/configs/abilities/air/AirSuctionConfig.java @@ -7,7 +7,8 @@ public class AirSuctionConfig extends AbilityConfig { public final long Cooldown = 500; public final int AnimationParticleAmount = 5; public final int SelectionParticleAmount = 5; - public final double PushFactor = 2.5; + public final double PushFactor_Self = 2.0; + public final double PushFactor_Others = 1.3; public final double Speed = 25; public final double Range = 20; public final double SelectionRange = 10; @@ -29,4 +30,4 @@ public class AirSuctionConfig extends AbilityConfig { return new String[] { "Abilities", "Air" }; } -} \ No newline at end of file +} diff --git a/src/com/projectkorra/projectkorra/configuration/configs/abilities/air/SuffocateConfig.java b/src/com/projectkorra/projectkorra/configuration/configs/abilities/air/SuffocateConfig.java index 54fdca02..96aca238 100644 --- a/src/com/projectkorra/projectkorra/configuration/configs/abilities/air/SuffocateConfig.java +++ b/src/com/projectkorra/projectkorra/configuration/configs/abilities/air/SuffocateConfig.java @@ -4,7 +4,7 @@ import com.projectkorra.projectkorra.configuration.configs.abilities.AbilityConf public class SuffocateConfig extends AbilityConfig { - public final long Cooldown = 0; + public final long Cooldown = 6500; public final boolean RequireConstantAim = true; public final double ConstantAimRadius = 3; public final boolean CanSuffocateUndead = false; @@ -42,4 +42,4 @@ public class SuffocateConfig extends AbilityConfig { return new String[] { "Abilities", "Air" }; } -} \ No newline at end of file +} diff --git a/src/com/projectkorra/projectkorra/cooldown/CooldownManager.java b/src/com/projectkorra/projectkorra/cooldown/CooldownManager.java new file mode 100644 index 00000000..bbd93822 --- /dev/null +++ b/src/com/projectkorra/projectkorra/cooldown/CooldownManager.java @@ -0,0 +1,193 @@ +package com.projectkorra.projectkorra.cooldown; + +import com.projectkorra.projectkorra.event.PlayerCooldownChangeEvent; +import com.projectkorra.projectkorra.module.DatabaseModule; +import com.projectkorra.projectkorra.module.ModuleManager; +import com.projectkorra.projectkorra.player.BendingPlayer; +import com.projectkorra.projectkorra.player.BendingPlayerLoadedEvent; +import com.projectkorra.projectkorra.player.BendingPlayerManager; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.player.PlayerQuitEvent; + +import java.sql.SQLException; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.PriorityQueue; +import java.util.UUID; +import java.util.function.Function; + +public class CooldownManager extends DatabaseModule { + + private final BendingPlayerManager bendingPlayerManager; + + private final Map> cooldownMap = new HashMap<>(); + private final Map> cooldownQueue = new HashMap<>(); + + private final Function> queueFunction = uuid -> new PriorityQueue<>(Comparator.comparing(cooldown -> cooldown.ExpireTime)); + + private CooldownManager() { + super("Cooldown", new CooldownRepository()); + + this.bendingPlayerManager = ModuleManager.getModule(BendingPlayerManager.class); + + runAsync(() -> { + try { + getRepository().createTables(); + } catch (SQLException e) { + e.printStackTrace(); + } + }); + + runTimer(() -> { + this.cooldownQueue.forEach((uuid, cooldowns) -> { + long currentTime = System.currentTimeMillis(); + + while (!cooldowns.isEmpty()) { + Cooldown cooldown = cooldowns.peek(); + + if (currentTime < cooldown.ExpireTime) { + break; + } + + this.cooldownMap.get(uuid).remove(cooldown.AbilityName); + cooldowns.poll(); + + if (cooldown.Permanent) { + int playerId = this.bendingPlayerManager.getBendingPlayer(uuid).getId(); + + runAsync(() -> { + try { + getRepository().deleteCooldown(playerId, cooldown.AbilityName); + } catch (SQLException e) { + e.printStackTrace(); + } + }); + } + } + }); + + this.cooldownMap.values().removeIf(Map::isEmpty); + this.cooldownQueue.values().removeIf(PriorityQueue::isEmpty); + }, 1, 1); + } + + @EventHandler + public void onBendingPlayerLoaded(BendingPlayerLoadedEvent event) { + Player player = event.getPlayer(); + BendingPlayer bendingPlayer = event.getBendingPlayer(); + + runAsync(() -> { + try { + Map cooldowns = getRepository().selectCooldowns(bendingPlayer.getId()); + + this.cooldownMap.computeIfAbsent(player.getUniqueId(), k -> new HashMap<>()).putAll(cooldowns); + this.cooldownQueue.computeIfAbsent(player.getUniqueId(), queueFunction).addAll(cooldowns.values()); + } catch (SQLException e) { + e.printStackTrace(); + } + }); + } + + @EventHandler + public void onQuit(PlayerQuitEvent event) { + this.cooldownMap.remove(event.getPlayer().getUniqueId()); + this.cooldownQueue.remove(event.getPlayer().getUniqueId()); + } + + public void addCooldown(Player player, String abilityName, long duration, boolean permanent) { + if (duration <= 0) { + return; + } + + PlayerCooldownChangeEvent event = new PlayerCooldownChangeEvent(player, abilityName, duration, PlayerCooldownChangeEvent.Result.ADDED); + getPlugin().getServer().getPluginManager().callEvent(event); + + if (event.isCancelled()) { + return; + } + + long expireTime = System.currentTimeMillis() + duration; + Cooldown cooldown = new Cooldown(abilityName, expireTime, permanent); + + this.cooldownMap.computeIfAbsent(player.getUniqueId(), k -> new HashMap<>()).put(abilityName, cooldown); + this.cooldownQueue.computeIfAbsent(player.getUniqueId(), queueFunction).add(cooldown); + + if (permanent) { + int playerId = this.bendingPlayerManager.getBendingPlayer(player).getId(); + + runAsync(() -> { + try { + getRepository().insertCooldown(playerId, abilityName, expireTime); + } catch (SQLException e) { + e.printStackTrace(); + } + }); + } + } + + public long getCooldown(Player player, String abilityName) { + Map cooldowns = this.cooldownMap.get(player.getUniqueId()); + + if (cooldowns != null && cooldowns.containsKey(abilityName)) { + return cooldowns.get(abilityName).ExpireTime; + } + + return -1L; + } + + public boolean isOnCooldown(Player player, String abilityName) { + Map cooldowns = this.cooldownMap.get(player.getUniqueId()); + + return cooldowns != null && cooldowns.containsKey(abilityName); + } + + public void removeCooldown(Player player, String abilityName) { + UUID uuid = player.getUniqueId(); + Map cooldowns = this.cooldownMap.get(player.getUniqueId()); + + if (cooldowns == null) { + return; + } + + Cooldown cooldown = cooldowns.remove(abilityName); + + if (cooldown == null) { + return; + } + + if (this.cooldownQueue.containsKey(uuid)) { + this.cooldownQueue.get(uuid).remove(cooldown); + } + + if (cooldown.Permanent) { + int playerId = this.bendingPlayerManager.getBendingPlayer(player).getId(); + + runAsync(() -> { + try { + getRepository().deleteCooldown(playerId, cooldown.AbilityName); + } catch (SQLException e) { + e.printStackTrace(); + } + }); + } + } + + public static class Cooldown { + + final String AbilityName; + final long ExpireTime; + final boolean Permanent; + + public Cooldown(String abilityName, long expireTime) { + this(abilityName, expireTime, false); + } + + public Cooldown(String abilityName, long expireTime, boolean permanent) { + AbilityName = abilityName; + ExpireTime = expireTime; + Permanent = permanent; + } + } +} diff --git a/src/com/projectkorra/projectkorra/cooldown/CooldownRepository.java b/src/com/projectkorra/projectkorra/cooldown/CooldownRepository.java new file mode 100644 index 00000000..859237da --- /dev/null +++ b/src/com/projectkorra/projectkorra/cooldown/CooldownRepository.java @@ -0,0 +1,82 @@ +package com.projectkorra.projectkorra.cooldown; + +import com.projectkorra.projectkorra.database.DatabaseQuery; +import com.projectkorra.projectkorra.database.DatabaseRepository; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; + +public class CooldownRepository extends DatabaseRepository { + + private static final DatabaseQuery CREATE_TABLE_COOLDOWNS = DatabaseQuery.newBuilder() + .query("CREATE TABLE IF NOT EXISTS pk_cooldowns (player_id INTEGER REFERENCES pk_bending_players (player_id), ability_name VARCHAR(100) NOT NULL, expire_time BIGINT NOT NULL, PRIMARY KEY (player_id, ability_name));") + .build(); + + private static final DatabaseQuery SELECT_COOLDOWNS = DatabaseQuery.newBuilder() + .query("SELECT * FROM pk_cooldowns WHERE player_id = ?") + .build(); + + private static final DatabaseQuery INSERT_COOLDOWN = DatabaseQuery.newBuilder() + .query("INSERT INTO pk_cooldowns VALUES (?, ?, ?);") + .build(); + + private static final DatabaseQuery DELETE_COOLDOWN = DatabaseQuery.newBuilder() + .query("DELETE FROM pk_cooldowns WHERE player_id = ? AND ability_name = ?;") + .build(); + + protected void createTables() throws SQLException { + Connection connection = getDatabase().getConnection(); + + try (PreparedStatement statement = connection.prepareStatement(CREATE_TABLE_COOLDOWNS.getQuery())) { + statement.executeUpdate(); + } + } + + protected Map selectCooldowns(int playerId) throws SQLException { + Connection connection = getDatabase().getConnection(); + + try (PreparedStatement statement = connection.prepareStatement(SELECT_COOLDOWNS.getQuery())) { + statement.setInt(1, playerId); + + Map cooldowns = new HashMap<>(); + + try (ResultSet rs = statement.executeQuery()) { + while (rs.next()) { + String abilityName = rs.getString("ability_name"); + long expireTime = rs.getLong("expire_time"); + + cooldowns.put(abilityName, new CooldownManager.Cooldown(abilityName, expireTime, true)); + } + + return cooldowns; + } + } + } + + protected void insertCooldown(int playerId, String abilityName, long expireTime) throws SQLException { + Connection connection = getDatabase().getConnection(); + + try (PreparedStatement statement = connection.prepareStatement(INSERT_COOLDOWN.getQuery())) { + statement.setInt(1, playerId); + statement.setString(2, abilityName); + statement.setLong(3, expireTime); + + statement.executeUpdate(); + } + } + + protected void deleteCooldown(int playerId, String abilityName) throws SQLException { + Connection connection = getDatabase().getConnection(); + + try (PreparedStatement statement = connection.prepareStatement(DELETE_COOLDOWN.getQuery())) { + statement.setInt(1, playerId); + statement.setString(2, abilityName); + + statement.executeUpdate(); + } + } +} diff --git a/src/com/projectkorra/projectkorra/database/DatabaseConfig.java b/src/com/projectkorra/projectkorra/database/DatabaseConfig.java new file mode 100644 index 00000000..c7c781e8 --- /dev/null +++ b/src/com/projectkorra/projectkorra/database/DatabaseConfig.java @@ -0,0 +1,26 @@ +package com.projectkorra.projectkorra.database; + +import com.projectkorra.projectkorra.configuration.Config; + +public class DatabaseConfig implements Config { + + public final DatabaseManager.Engine Engine = DatabaseManager.Engine.SQLITE; + + public final String SQLite_File = "projectkorra.sql"; + + public final String MySQL_IP = "localhost"; + public final String MySQL_Port = "3306"; + public final String MySQL_DatabaseName = "projectkorra"; + public final String MySQL_Username = "root"; + public final String MySQL_Password = "password"; + + @Override + public String getName() { + return "Database"; + } + + @Override + public String[] getParents() { + return new String[0]; + } +} diff --git a/src/com/projectkorra/projectkorra/database/DatabaseManager.java b/src/com/projectkorra/projectkorra/database/DatabaseManager.java new file mode 100644 index 00000000..78748b8c --- /dev/null +++ b/src/com/projectkorra/projectkorra/database/DatabaseManager.java @@ -0,0 +1,52 @@ +package com.projectkorra.projectkorra.database; + +import com.projectkorra.projectkorra.configuration.ConfigManager; +import com.projectkorra.projectkorra.database.engine.MySQLDatabase; +import com.projectkorra.projectkorra.database.engine.SQLDatabase; +import com.projectkorra.projectkorra.database.engine.SQLiteDatabase; +import com.projectkorra.projectkorra.module.Module; + +import java.util.logging.Level; + +public class DatabaseManager extends Module { + + private final DatabaseConfig config; + private final SQLDatabase database; + + private DatabaseManager() { + super("Database"); + + this.config = ConfigManager.getConfig(DatabaseConfig.class); + + switch (this.config.Engine) { + case MYSQL: + this.database = new MySQLDatabase(this.config); + break; + case SQLITE: + this.database = new SQLiteDatabase(this, this.config); + break; + default: + log(Level.SEVERE, "Unknown database engine."); + this.database = null; + break; + } + } + + public DatabaseConfig getConfig() { + return this.config; + } + + public SQLDatabase getDatabase() { + return this.database; + } + + @Override + public void onDisable() { + this.database.close(); + } + + public enum Engine { + MYSQL, + SQLITE; + } +} diff --git a/src/com/projectkorra/projectkorra/database/DatabaseQuery.java b/src/com/projectkorra/projectkorra/database/DatabaseQuery.java new file mode 100644 index 00000000..9eb6f970 --- /dev/null +++ b/src/com/projectkorra/projectkorra/database/DatabaseQuery.java @@ -0,0 +1,55 @@ +package com.projectkorra.projectkorra.database; + +import com.projectkorra.projectkorra.module.ModuleManager; + +public class DatabaseQuery { + + private final String mysql; + private final String sqlite; + + private DatabaseQuery(String mysql, String sqlite) { + this.mysql = mysql; + this.sqlite = sqlite; + } + + public String getQuery() { + switch (ModuleManager.getModule(DatabaseManager.class).getConfig().Engine) { + case MYSQL: + return this.mysql; + case SQLITE: + return this.sqlite; + } + + return null; + } + + public static Builder newBuilder() { + return new Builder(); + } + + public static final class Builder { + + private String mysql; + private String sqlite; + + public Builder mysql(String mysql) { + this.mysql = mysql; + return this; + } + + public Builder sqlite(String sqlite) { + this.sqlite = sqlite; + return this; + } + + public Builder query(String query) { + this.mysql = query; + this.sqlite = query; + return this; + } + + public DatabaseQuery build() { + return new DatabaseQuery(this.mysql, this.sqlite); + } + } +} diff --git a/src/com/projectkorra/projectkorra/database/DatabaseRepository.java b/src/com/projectkorra/projectkorra/database/DatabaseRepository.java new file mode 100644 index 00000000..83a9859c --- /dev/null +++ b/src/com/projectkorra/projectkorra/database/DatabaseRepository.java @@ -0,0 +1,17 @@ +package com.projectkorra.projectkorra.database; + +import com.projectkorra.projectkorra.database.engine.SQLDatabase; +import com.projectkorra.projectkorra.module.ModuleManager; + +public abstract class DatabaseRepository { + + private final DatabaseManager databaseManager; + + public DatabaseRepository() { + this.databaseManager = ModuleManager.getModule(DatabaseManager.class); + } + + protected SQLDatabase getDatabase() { + return this.databaseManager.getDatabase(); + } +} diff --git a/src/com/projectkorra/projectkorra/database/engine/MySQLDatabase.java b/src/com/projectkorra/projectkorra/database/engine/MySQLDatabase.java new file mode 100644 index 00000000..da2f07c7 --- /dev/null +++ b/src/com/projectkorra/projectkorra/database/engine/MySQLDatabase.java @@ -0,0 +1,42 @@ +package com.projectkorra.projectkorra.database.engine; + +import com.projectkorra.projectkorra.database.DatabaseConfig; +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; + +import java.sql.Connection; +import java.sql.SQLException; + +public class MySQLDatabase implements SQLDatabase { + + private final HikariDataSource hikari; + + public MySQLDatabase(DatabaseConfig databaseConfig) { + HikariConfig hikariConfig = new HikariConfig(); + + hikariConfig.setJdbcUrl("jdbc:mysql://" + databaseConfig.MySQL_IP + ":" + databaseConfig.MySQL_Port + "/" + databaseConfig.MySQL_DatabaseName); + hikariConfig.setDriverClassName("com.mysql.jdbc.Driver"); + hikariConfig.setUsername(databaseConfig.MySQL_Username); + hikariConfig.setPassword(databaseConfig.MySQL_Password); + hikariConfig.setMinimumIdle(1); + hikariConfig.setMaximumPoolSize(10); + hikariConfig.setConnectionTimeout(10000); + + this.hikari = new HikariDataSource(hikariConfig); + } + + @Override + public Connection getConnection() { + try (Connection connection = this.hikari.getConnection()) { + return connection; + } catch (SQLException e) { + e.printStackTrace(); + return null; + } + } + + @Override + public void close() { + this.hikari.close(); + } +} diff --git a/src/com/projectkorra/projectkorra/database/engine/SQLDatabase.java b/src/com/projectkorra/projectkorra/database/engine/SQLDatabase.java new file mode 100644 index 00000000..117dbb02 --- /dev/null +++ b/src/com/projectkorra/projectkorra/database/engine/SQLDatabase.java @@ -0,0 +1,10 @@ +package com.projectkorra.projectkorra.database.engine; + +import java.sql.Connection; + +public interface SQLDatabase { + + Connection getConnection(); + + void close(); +} diff --git a/src/com/projectkorra/projectkorra/database/engine/SQLiteDatabase.java b/src/com/projectkorra/projectkorra/database/engine/SQLiteDatabase.java new file mode 100644 index 00000000..c86f9c5b --- /dev/null +++ b/src/com/projectkorra/projectkorra/database/engine/SQLiteDatabase.java @@ -0,0 +1,64 @@ +package com.projectkorra.projectkorra.database.engine; + +import com.projectkorra.projectkorra.database.DatabaseConfig; +import com.projectkorra.projectkorra.database.DatabaseManager; + +import java.io.File; +import java.io.IOException; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; + +public class SQLiteDatabase implements SQLDatabase { + + private final File databaseFile; + private Connection connection; + + public SQLiteDatabase(DatabaseManager databaseManager, DatabaseConfig databaseConfig) { + this.databaseFile = new File(databaseManager.getPlugin().getDataFolder(), databaseConfig.SQLite_File); + + if (!this.databaseFile.getParentFile().exists()) { + this.databaseFile.getParentFile().mkdirs(); + } + + if (!this.databaseFile.exists()) { + try { + this.databaseFile.createNewFile(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + open(); + } + + public void open() { + try { + this.connection = DriverManager.getConnection("jdbc:sqlite:" + databaseFile.getAbsolutePath()); + } catch (SQLException e) { + e.printStackTrace(); + } + } + + @Override + public Connection getConnection() { + try { + if (this.connection == null || this.connection.isClosed()) { + open(); + } + } catch (SQLException e) { + e.printStackTrace(); + } + + return this.connection; + } + + @Override + public void close() { + try { + this.connection.close(); + } catch (SQLException e) { + e.printStackTrace(); + } + } +} diff --git a/src/com/projectkorra/projectkorra/earthbending/Catapult.java b/src/com/projectkorra/projectkorra/earthbending/Catapult.java index 044add6b..9e624c4b 100644 --- a/src/com/projectkorra/projectkorra/earthbending/Catapult.java +++ b/src/com/projectkorra/projectkorra/earthbending/Catapult.java @@ -11,6 +11,7 @@ import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.util.Vector; +import com.projectkorra.projectkorra.command.Commands; import com.projectkorra.projectkorra.GeneralMethods; import com.projectkorra.projectkorra.ability.EarthAbility; import com.projectkorra.projectkorra.attribute.Attribute; @@ -70,6 +71,9 @@ public class Catapult extends EarthAbility { private void moveEarth(final Vector apply, final Vector direction) { for (final Entity entity : GeneralMethods.getEntitiesAroundPoint(this.origin, 2)) { if (entity.getEntityId() != this.player.getEntityId()) { + if (GeneralMethods.isRegionProtectedFromBuild(this, entity.getLocation()) || ((entity instanceof Player) && Commands.invincible.contains(((Player) entity).getName()))) { + continue; + } entity.setVelocity(apply); } } diff --git a/src/com/projectkorra/projectkorra/earthbending/Collapse.java b/src/com/projectkorra/projectkorra/earthbending/Collapse.java index 42db5e40..a2e879ed 100644 --- a/src/com/projectkorra/projectkorra/earthbending/Collapse.java +++ b/src/com/projectkorra/projectkorra/earthbending/Collapse.java @@ -99,7 +99,7 @@ public class Collapse extends EarthAbility { thisBlock = this.block.getWorld().getBlockAt(this.location.clone().add(this.direction.clone().multiply(-i))); this.affectedBlocks.put(thisBlock, thisBlock); if (RaiseEarth.blockInAllAffectedBlocks(thisBlock)) { - EarthAbility.revertBlock(thisBlock); + RaiseEarth.revertAffectedBlock(thisBlock); } } } diff --git a/src/com/projectkorra/projectkorra/earthbending/EarthBlast.java b/src/com/projectkorra/projectkorra/earthbending/EarthBlast.java index 43c32723..16e3cb9d 100644 --- a/src/com/projectkorra/projectkorra/earthbending/EarthBlast.java +++ b/src/com/projectkorra/projectkorra/earthbending/EarthBlast.java @@ -5,6 +5,7 @@ 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; @@ -376,8 +377,12 @@ public class EarthBlast extends EarthAbility { this.firstDestination = this.location.clone(); if (this.destination.getY() - this.location.getY() > 2) { this.firstDestination.setY(this.destination.getY() - 1); - } else { + } 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) { diff --git a/src/com/projectkorra/projectkorra/earthbending/RaiseEarth.java b/src/com/projectkorra/projectkorra/earthbending/RaiseEarth.java index d5433629..e89a2ed7 100644 --- a/src/com/projectkorra/projectkorra/earthbending/RaiseEarth.java +++ b/src/com/projectkorra/projectkorra/earthbending/RaiseEarth.java @@ -2,7 +2,6 @@ package com.projectkorra.projectkorra.earthbending; import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.bukkit.Location; @@ -15,11 +14,10 @@ import com.projectkorra.projectkorra.attribute.Attribute; import com.projectkorra.projectkorra.configuration.configs.abilities.earth.RaiseEarthConfig; import com.projectkorra.projectkorra.util.BlockSource; import com.projectkorra.projectkorra.util.ClickType; +import com.projectkorra.projectkorra.util.TempBlock; public class RaiseEarth extends EarthAbility { - private static final Map ALL_AFFECTED_BLOCKS = new ConcurrentHashMap<>(); - private int distance; @Attribute(Attribute.HEIGHT) private int height; @@ -104,7 +102,7 @@ public class RaiseEarth extends EarthAbility { private boolean canInstantiate() { for (final Block block : this.affectedBlocks.keySet()) { - if (!this.isEarthbendable(block) || ALL_AFFECTED_BLOCKS.containsKey(block)) { + if (!this.isEarthbendable(block) || TempBlock.isTempBlock(block)) { return false; } } @@ -143,11 +141,15 @@ public class RaiseEarth extends EarthAbility { } public static boolean blockInAllAffectedBlocks(final Block block) { - return ALL_AFFECTED_BLOCKS.containsKey(block); + for (RaiseEarth raiseEarth : getAbilities(RaiseEarth.class)) { + if (raiseEarth.affectedBlocks.contains(block)) { + return true; + } + } + return false; } public static void revertAffectedBlock(final Block block) { - ALL_AFFECTED_BLOCKS.remove(block); for (final RaiseEarth raiseEarth : getAbilities(RaiseEarth.class)) { raiseEarth.affectedBlocks.remove(block); } diff --git a/src/com/projectkorra/projectkorra/earthbending/metal/MetalClips.java b/src/com/projectkorra/projectkorra/earthbending/metal/MetalClips.java index e2e5eab4..f65e30c8 100644 --- a/src/com/projectkorra/projectkorra/earthbending/metal/MetalClips.java +++ b/src/com/projectkorra/projectkorra/earthbending/metal/MetalClips.java @@ -18,6 +18,7 @@ import org.bukkit.entity.Zombie; import org.bukkit.inventory.ItemStack; import org.bukkit.util.Vector; +import com.projectkorra.projectkorra.command.Commands; import com.projectkorra.projectkorra.GeneralMethods; import com.projectkorra.projectkorra.ability.CoreAbility; import com.projectkorra.projectkorra.ability.MetalAbility; @@ -166,7 +167,7 @@ public class MetalClips extends MetalAbility { return; } else if (this.metalClipsCount == 3 && !this.canUse4Clips) { return; - } else if (this.targetEntity != null && GeneralMethods.isRegionProtectedFromBuild(this, this.targetEntity.getLocation())) { + } else if (this.targetEntity != null && (GeneralMethods.isRegionProtectedFromBuild(this, this.targetEntity.getLocation()) || ((targetEntity instanceof Player) && Commands.invincible.contains(((Player) targetEntity).getName())))) { return; } diff --git a/src/com/projectkorra/projectkorra/element/Element.java b/src/com/projectkorra/projectkorra/element/Element.java new file mode 100644 index 00000000..01d05579 --- /dev/null +++ b/src/com/projectkorra/projectkorra/element/Element.java @@ -0,0 +1,38 @@ +package com.projectkorra.projectkorra.element; + +import org.bukkit.ChatColor; + +public class Element { + + private final int elementId; + private final String elementName; + private final String displayName; + private final ChatColor color; + + public Element(int elementId, String elementName, String displayName, ChatColor color) { + this.elementId = elementId; + this.elementName = elementName; + this.displayName = displayName; + this.color = color; + } + + public int getId() { + return this.elementId; + } + + public String getName() { + return this.elementName; + } + + public String getDisplayName() { + return this.displayName; + } + + public ChatColor getColor() { + return this.color; + } + + public String getColoredName() { + return this.color + this.displayName; + } +} diff --git a/src/com/projectkorra/projectkorra/element/ElementManager.java b/src/com/projectkorra/projectkorra/element/ElementManager.java new file mode 100644 index 00000000..7d638d80 --- /dev/null +++ b/src/com/projectkorra/projectkorra/element/ElementManager.java @@ -0,0 +1,271 @@ +package com.projectkorra.projectkorra.element; + +import com.google.common.base.Preconditions; +import com.projectkorra.projectkorra.module.DatabaseModule; +import com.projectkorra.projectkorra.module.ModuleManager; +import com.projectkorra.projectkorra.player.BendingPlayer; +import com.projectkorra.projectkorra.player.BendingPlayerLoadedEvent; +import com.projectkorra.projectkorra.player.BendingPlayerManager; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; + +import java.sql.SQLException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public class ElementManager extends DatabaseModule { + + private static final String WATER = "water", EARTH = "earth", FIRE = "fire", AIR = "air", CHI = "chi", AVATAR = "avatar"; + private static final String BLOOD = "blood", HEALING = "healing", ICE = "ice", PLANT = "plant"; + private static final String LAVA = "lava", METAL = "metal", SAND = "sand"; + private static final String COMBUSTION = "combustion", LIGHTNING = "lightning"; + private static final String FLIGHT = "flight", SPIRITUAL = "spiritual"; + + private final BendingPlayerManager bendingPlayerManager; + + private final Map elements = new HashMap<>(); + private final Map names = new HashMap<>(); + + private final String nameRegex = "[a-zA-Z]+"; + + private Element water, earth, fire, air, chi, avatar; + private SubElement blood, healing, ice, plant; + private SubElement lava, metal, sand; + private SubElement combustion, lightning; + private SubElement flight, spiritual; + + private ElementManager() { + super("Element", new ElementRepository()); + + this.bendingPlayerManager = ModuleManager.getModule(BendingPlayerManager.class); + + runAsync(() -> { + try { + getRepository().createTables(); + + // Waterbending + this.water = addElement(WATER, "Water", ChatColor.AQUA); + this.blood = addSubElement(BLOOD, "Blood", ChatColor.DARK_AQUA, this.water); + this.healing = addSubElement(HEALING, "Healing", ChatColor.DARK_AQUA, this.water); + this.ice = addSubElement(ICE, "Ice", ChatColor.DARK_AQUA, this.water); + this.plant = addSubElement(PLANT, "Plant", ChatColor.DARK_AQUA, this.water); + + // Earthbending + this.earth = addElement(EARTH, "Earth", ChatColor.AQUA); + this.lava = addSubElement(LAVA, "Lava", ChatColor.DARK_GREEN, this.earth); + this.metal = addSubElement(METAL, "Metal", ChatColor.DARK_GREEN, this.earth); + this.sand = addSubElement(SAND, "Sand", ChatColor.DARK_GREEN, this.earth); + + // Firebending + this.fire = addElement(FIRE, "Fire", ChatColor.RED); + this.combustion = addSubElement(COMBUSTION, "Combustion", ChatColor.DARK_RED, this.fire); + this.lightning = addSubElement(LIGHTNING, "Lightning", ChatColor.DARK_RED, this.fire); + + // Airbending + this.air = addElement(AIR, "Air", ChatColor.GRAY); + this.flight = addSubElement(FLIGHT, "Flight", ChatColor.DARK_GRAY, this.air); + this.spiritual = addSubElement(SPIRITUAL, "Spiritual", ChatColor.DARK_GRAY, this.air); + + // Chiblocking + this.chi = addElement(CHI, "Chi", ChatColor.GOLD); + + // Avatar + this.avatar = addElement(AVATAR, "Avatar", ChatColor.DARK_PURPLE); + } catch (SQLException e) { + e.printStackTrace(); + } + + runSync(() -> { + log("Populated element database tables."); + }); + }); + } + + @EventHandler + public void onBendingPlayerLoaded(BendingPlayerLoadedEvent event) { + BendingPlayer bendingPlayer = event.getBendingPlayer(); + + runAsync(() -> { + try { + List elements = getRepository().selectPlayerElements(bendingPlayer.getId()).stream().map(this.elements::get).collect(Collectors.toList()); + + elements.forEach(bendingPlayer::addElement); + } catch (SQLException e) { + e.printStackTrace(); + } + }); + } + + public boolean addElement(Player player, Element element) { + BendingPlayer bendingPlayer = this.bendingPlayerManager.getBendingPlayer(player); + + if (!bendingPlayer.addElement(element)) { + return false; + } + + runAsync(() -> { + try { + getRepository().insertPlayerElement(bendingPlayer.getId(), element.getId()); + } catch (SQLException e) { + e.printStackTrace(); + } + }); + + return true; + } + + public void setElement(Player player, Element element) { + BendingPlayer bendingPlayer = this.bendingPlayerManager.getBendingPlayer(player); + + bendingPlayer.clearElements(); + bendingPlayer.addElement(element); + + runAsync(() -> { + try { + getRepository().deletePlayerElements(bendingPlayer.getId()); + getRepository().insertPlayerElement(bendingPlayer.getId(), element.getId()); + } catch (SQLException e) { + e.printStackTrace(); + } + }); + } + + public boolean removeElement(Player player, Element element) { + BendingPlayer bendingPlayer = this.bendingPlayerManager.getBendingPlayer(player); + + if (!bendingPlayer.removeElement(element)) { + return false; + } + + runAsync(() -> { + try { + getRepository().deletePlayerElement(bendingPlayer.getId(), element.getId()); + } catch (SQLException e) { + e.printStackTrace(); + } + }); + + return true; + } + + public void clearElements(Player player) { + BendingPlayer bendingPlayer = this.bendingPlayerManager.getBendingPlayer(player); + + bendingPlayer.clearElements(); + + runAsync(() -> { + try { + getRepository().deletePlayerElements(bendingPlayer.getId()); + } catch (SQLException e) { + e.printStackTrace(); + } + }); + } + + private Element addElement(String elementName, String displayName, ChatColor color) { + int elementId = registerElement(elementName); + + Element element = new Element(elementId, elementName, displayName, color); + + this.elements.put(elementId, element); + this.names.put(elementName, element); + + return element; + } + + private SubElement addSubElement(String elementName, String displayName, ChatColor color, Element parent) { + int elementId = registerElement(elementName); + + SubElement element = new SubElement(elementId, elementName, displayName, color, parent); + + this.elements.put(elementId, element); + this.names.put(elementName, element); + + return element; + } + + private int registerElement(String elementName) { + Preconditions.checkNotNull(elementName, "Element name cannot be null"); + + Preconditions.checkArgument(Pattern.matches(this.nameRegex, elementName), "Element name must only contain letters and spaces"); + + try { + return getRepository().selectElemenetId(elementName); + } catch (SQLException e) { + e.printStackTrace(); + return -1; + } + } + + public Element getWater() { + return this.water; + } + + public SubElement getBlood() { + return this.blood; + } + + public SubElement getHealing() { + return this.healing; + } + + public SubElement getIce() { + return this.ice; + } + + public SubElement getPlant() { + return this.plant; + } + + public Element getEarth() { + return this.earth; + } + + public SubElement getLava() { + return this.lava; + } + + public SubElement getMetal() { + return this.metal; + } + + public SubElement getSand() { + return this.sand; + } + + public Element getFire() { + return this.fire; + } + + public SubElement getCombustion() { + return this.combustion; + } + + public SubElement getLightning() { + return this.lightning; + } + + public Element getAir() { + return this.air; + } + + public SubElement getFlight() { + return this.flight; + } + + public SubElement getSpiritual() { + return this.spiritual; + } + + public Element getChi() { + return this.chi; + } + + public Element getAvatar() { + return this.avatar; + } +} diff --git a/src/com/projectkorra/projectkorra/element/ElementRepository.java b/src/com/projectkorra/projectkorra/element/ElementRepository.java new file mode 100644 index 00000000..2d35f9a1 --- /dev/null +++ b/src/com/projectkorra/projectkorra/element/ElementRepository.java @@ -0,0 +1,136 @@ +package com.projectkorra.projectkorra.element; + +import com.projectkorra.projectkorra.database.DatabaseQuery; +import com.projectkorra.projectkorra.database.DatabaseRepository; + +import java.sql.*; +import java.util.HashSet; +import java.util.Set; + +public class ElementRepository extends DatabaseRepository { + + private static final DatabaseQuery CREATE_TABLE_ELEMENTS = DatabaseQuery.newBuilder() + .mysql("CREATE TABLE IF NOT EXISTS pk_elements (element_id INTEGER PRIMARY KEY AUTO_INCREMENT, element_name VARCHAR(50) NOT NULL, UNIQUE INDEX name_index (element_name));") + .sqlite("CREATE TABLE IF NOT EXISTS pk_elements (element_id INTEGER PRIMARY KEY AUTOINCREMENT, element_name VARCHAR(50) NOT NULL); CREATE UNIQUE INDEX name_index ON pk_elements (element_name);") + .build(); + + private static final DatabaseQuery CREATE_TABLE_PLAYER_ELEMENTS = DatabaseQuery.newBuilder() + .mysql("CREATE TABLE IF NOT EXISTS pk_player_elements (player_id INTEGER REFERENCES pk_bending_players (player_id), element_id INTEGER REFERENCES pk_elements (element_id), PRIMARY KEY (player_id, element_id), INDEX player_index (player_id), INDEX element_index (element_id));") + .sqlite("CREATE TABLE IF NOT EXISTS pk_player_elements (player_id INTEGER REFERENCES pk_bending_players (player_id), element_id INTEGER REFERENCES pk_elements (element_id), PRIMARY KEY (player_id, element_id)); CREATE INDEX player_index ON pk_player_elements (player_id); CREATE INDEX element_index ON pk_player_elements (element_id);") + .build(); + + private static final DatabaseQuery SELECT_ELEMENT_ID = DatabaseQuery.newBuilder() + .query("SELECT element_id FROM pk_elements WHERE element_name = ?;") + .build(); + + private static final DatabaseQuery INSERT_ELEMENT_ID = DatabaseQuery.newBuilder() + .query("INSERT INTO pk_elements (element_name) VALUES (?);") + .build(); + + private static final DatabaseQuery SELECT_PLAYER_ELEMENTS = DatabaseQuery.newBuilder() + .query("SELECT element_id FROM pk_player_elements WHERE player_id = ?;") + .build(); + + private static final DatabaseQuery INSERT_PLAYER_ELEMENT = DatabaseQuery.newBuilder() + .query("INSERT INTO pk_player_elements VALUES (?, ?);") + .build(); + + private static final DatabaseQuery DELETE_PLAYER_ELEMENTS = DatabaseQuery.newBuilder() + .query("DELETE FROM pk_player_elements WHERE player_id = ?;") + .build(); + + private static final DatabaseQuery DELETE_PLAYER_ELEMENT = DatabaseQuery.newBuilder() + .query("DELETE FROM pk_player_elements WHERE player_id = ? AND element_id = ?;") + .build(); + + protected void createTables() throws SQLException { + Connection connection = getDatabase().getConnection(); + + try (PreparedStatement elements = connection.prepareStatement(CREATE_TABLE_ELEMENTS.getQuery()); PreparedStatement playerElements = connection.prepareStatement(CREATE_TABLE_PLAYER_ELEMENTS.getQuery())) { + elements.executeUpdate(); + playerElements.executeUpdate(); + } + } + + protected int selectElemenetId(String elementName) throws SQLException { + Connection connection = getDatabase().getConnection(); + + try (PreparedStatement statement = connection.prepareStatement(SELECT_ELEMENT_ID.getQuery())) { + statement.setString(1, elementName); + + try (ResultSet rs = statement.executeQuery()) { + if (!rs.next()) { + return insertElementId(elementName); + } + + return rs.getInt("element_id"); + } + } + } + + private int insertElementId(String elementName) throws SQLException { + Connection connection = getDatabase().getConnection(); + + try (PreparedStatement statement = connection.prepareStatement(INSERT_ELEMENT_ID.getQuery(), Statement.RETURN_GENERATED_KEYS)) { + statement.setString(1, elementName); + + statement.executeUpdate(); + + try (ResultSet rs = statement.getGeneratedKeys()) { + rs.next(); + + return rs.getInt(1); + } + } + } + + protected Set selectPlayerElements(int playerId) throws SQLException { + Connection connection = getDatabase().getConnection(); + + try (PreparedStatement statement = connection.prepareStatement(SELECT_PLAYER_ELEMENTS.getQuery())) { + statement.setInt(1, playerId); + + Set elements = new HashSet<>(); + + try (ResultSet rs = statement.executeQuery()) { + while (rs.next()) { + elements.add(rs.getInt("element_id")); + } + + return elements; + } + } + } + + protected void insertPlayerElement(int playerId, int elementId) throws SQLException { + Connection connection = getDatabase().getConnection(); + + try (PreparedStatement statement = connection.prepareStatement(INSERT_PLAYER_ELEMENT.getQuery())) { + statement.setInt(1, playerId); + statement.setInt(2, elementId); + + statement.executeUpdate(); + } + } + + protected void deletePlayerElements(int playerId) throws SQLException { + Connection connection = getDatabase().getConnection(); + + try (PreparedStatement statement = connection.prepareStatement(DELETE_PLAYER_ELEMENTS.getQuery())) { + statement.setInt(1, playerId); + + statement.executeUpdate(); + } + } + + protected void deletePlayerElement(int playerId, int elementId) throws SQLException { + Connection connection = getDatabase().getConnection(); + + try (PreparedStatement statement = connection.prepareStatement(DELETE_PLAYER_ELEMENT.getQuery())) { + statement.setInt(1, playerId); + statement.setInt(2, elementId); + + statement.executeUpdate(); + } + } +} diff --git a/src/com/projectkorra/projectkorra/element/SubElement.java b/src/com/projectkorra/projectkorra/element/SubElement.java new file mode 100644 index 00000000..6854740a --- /dev/null +++ b/src/com/projectkorra/projectkorra/element/SubElement.java @@ -0,0 +1,18 @@ +package com.projectkorra.projectkorra.element; + +import org.bukkit.ChatColor; + +public class SubElement extends Element { + + private final Element parent; + + public SubElement(int elementId, String elementName, String displayName, ChatColor color, Element parent) { + super(elementId, elementName, displayName, color); + + this.parent = parent; + } + + public Element getParent() { + return this.parent; + } +} diff --git a/src/com/projectkorra/projectkorra/firebending/FireBlastCharged.java b/src/com/projectkorra/projectkorra/firebending/FireBlastCharged.java index 3fa43402..1b61e7a7 100644 --- a/src/com/projectkorra/projectkorra/firebending/FireBlastCharged.java +++ b/src/com/projectkorra/projectkorra/firebending/FireBlastCharged.java @@ -217,7 +217,7 @@ public class FireBlastCharged extends FireAbility { } boolean exploded = false; - for (final Entity entity : GeneralMethods.getEntitiesAroundPoint(this.location, 2 * this.collisionRadius)) { + for (final Entity entity : GeneralMethods.getEntitiesAroundPoint(this.location, this.collisionRadius)) { if (entity.getEntityId() == this.player.getEntityId() || GeneralMethods.isRegionProtectedFromBuild(this, entity.getLocation())) { continue; } @@ -311,7 +311,9 @@ public class FireBlastCharged extends FireAbility { @Override public void remove() { super.remove(); - this.bPlayer.addCooldown(this); + if (this.charged) { + this.bPlayer.addCooldown(this); + } } @Override diff --git a/src/com/projectkorra/projectkorra/firebending/FireJet.java b/src/com/projectkorra/projectkorra/firebending/FireJet.java index 6e9d235a..3fe40aad 100644 --- a/src/com/projectkorra/projectkorra/firebending/FireJet.java +++ b/src/com/projectkorra/projectkorra/firebending/FireJet.java @@ -74,10 +74,8 @@ public class FireJet extends FireAbility { this.time = System.currentTimeMillis(); this.start(); - + this.previousGlidingState = player.isGliding(); - - this.bPlayer.addCooldown(this); } } @@ -122,6 +120,7 @@ public class FireJet extends FireAbility { } this.flightHandler.removeInstance(this.player, this.getName()); this.player.setFallDistance(0); + this.bPlayer.addCooldown(this); } @Override diff --git a/src/com/projectkorra/projectkorra/firebending/WallOfFire.java b/src/com/projectkorra/projectkorra/firebending/WallOfFire.java index a8695d29..16a62b81 100644 --- a/src/com/projectkorra/projectkorra/firebending/WallOfFire.java +++ b/src/com/projectkorra/projectkorra/firebending/WallOfFire.java @@ -103,7 +103,6 @@ public class WallOfFire extends FireAbility { this.initializeBlocks(); this.start(); - this.bPlayer.addCooldown(this); } private void affect(final Entity entity) { @@ -211,6 +210,12 @@ public class WallOfFire extends FireAbility { } } + @Override + public void remove() { + super.remove(); + this.bPlayer.addCooldown(this); + } + @Override public String getName() { return "WallOfFire"; diff --git a/src/com/projectkorra/projectkorra/firebending/combo/JetBlast.java b/src/com/projectkorra/projectkorra/firebending/combo/JetBlast.java index a4421df1..5e044148 100644 --- a/src/com/projectkorra/projectkorra/firebending/combo/JetBlast.java +++ b/src/com/projectkorra/projectkorra/firebending/combo/JetBlast.java @@ -81,7 +81,6 @@ public class JetBlast extends FireAbility implements ComboAbilit return; } - this.bPlayer.addCooldown("JetBlast", this.cooldown); this.firstTime = false; final float spread = 0F; ParticleEffect.EXPLOSION_LARGE.display(this.player.getLocation(), 1, spread, spread, spread, 0); @@ -108,6 +107,7 @@ public class JetBlast extends FireAbility implements ComboAbilit task.remove(); } super.remove(); + this.bPlayer.addCooldown("JetBlast", this.cooldown); } @Override diff --git a/src/com/projectkorra/projectkorra/firebending/combo/JetBlaze.java b/src/com/projectkorra/projectkorra/firebending/combo/JetBlaze.java index 2063e3c3..bb4dd4a7 100644 --- a/src/com/projectkorra/projectkorra/firebending/combo/JetBlaze.java +++ b/src/com/projectkorra/projectkorra/firebending/combo/JetBlaze.java @@ -90,7 +90,6 @@ public class JetBlaze extends FireAbility implements ComboAbilit this.remove(); return; } - this.bPlayer.addCooldown("JetBlaze", this.cooldown); this.firstTime = false; } else if (System.currentTimeMillis() - this.time > this.duration) { this.remove(); @@ -105,7 +104,7 @@ public class JetBlaze extends FireAbility implements ComboAbilit fs.setDensity(8); fs.setSpread(1.0F); fs.setUseNewParticles(true); - fs.setCollisionRadius(3); + fs.setCollisionRadius(2); fs.setParticleEffect(ParticleEffect.SMOKE_LARGE); fs.setDamage(this.damage); fs.setFireTicks(this.fireTicks); @@ -123,6 +122,7 @@ public class JetBlaze extends FireAbility implements ComboAbilit task.remove(); } super.remove(); + this.bPlayer.addCooldown("JetBlaze", this.cooldown); } @Override diff --git a/src/com/projectkorra/projectkorra/firebending/lightning/Lightning.java b/src/com/projectkorra/projectkorra/firebending/lightning/Lightning.java index 5eb0801e..31b84960 100644 --- a/src/com/projectkorra/projectkorra/firebending/lightning/Lightning.java +++ b/src/com/projectkorra/projectkorra/firebending/lightning/Lightning.java @@ -3,6 +3,8 @@ package com.projectkorra.projectkorra.firebending.lightning; import java.util.ArrayList; import java.util.List; +import com.projectkorra.projectkorra.ability.CoreAbility; +import com.projectkorra.projectkorra.firebending.FireJet; import org.bukkit.Location; import org.bukkit.block.Block; import org.bukkit.entity.Entity; @@ -181,6 +183,9 @@ public class Lightning extends LightningAbility { } else if (!this.bPlayer.canBendIgnoreCooldowns(this)) { this.remove(); return; + } else if (CoreAbility.hasAbility(player, FireJet.class)){ + this.removeWithTasks(); + return; } this.locations.clear(); diff --git a/src/com/projectkorra/projectkorra/module/DatabaseModule.java b/src/com/projectkorra/projectkorra/module/DatabaseModule.java new file mode 100644 index 00000000..743f7b0b --- /dev/null +++ b/src/com/projectkorra/projectkorra/module/DatabaseModule.java @@ -0,0 +1,18 @@ +package com.projectkorra.projectkorra.module; + +import com.projectkorra.projectkorra.database.DatabaseRepository; + +public abstract class DatabaseModule extends Module { + + private final T repository; + + protected DatabaseModule(String name, T repository) { + super(name); + + this.repository = repository; + } + + protected T getRepository() { + return this.repository; + } +} diff --git a/src/com/projectkorra/projectkorra/module/Module.java b/src/com/projectkorra/projectkorra/module/Module.java new file mode 100644 index 00000000..f68e0f0a --- /dev/null +++ b/src/com/projectkorra/projectkorra/module/Module.java @@ -0,0 +1,88 @@ +package com.projectkorra.projectkorra.module; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.projectkorra.projectkorra.ProjectKorra; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.plugin.java.JavaPlugin; + +import java.util.logging.Level; + +public abstract class Module implements Listener { + + private static final String LOG_FORMAT = "(%s) %s"; + + private final String name; + private final Gson gson = new GsonBuilder().setPrettyPrinting().create(); + + protected Module(String name) { + this.name = name; + } + + protected final void enable() { + long startTime = System.currentTimeMillis(); + log("Enabling..."); + + getPlugin().getServer().getPluginManager().registerEvents(this, getPlugin()); + onEnable(); + + long finishTime = System.currentTimeMillis(); + log(String.format("Enabled! [%sms]", finishTime - startTime)); + } + + public void onEnable() { + + } + + protected final void disable() { + long startTime = System.currentTimeMillis(); + log("Disabling..."); + + HandlerList.unregisterAll(this); + onDisable(); + + long finishTime = System.currentTimeMillis(); + log(String.format("Disabled! [%sms]", finishTime - startTime)); + } + + public void onDisable() { + + } + + protected final void runSync(Runnable runnable) { + getPlugin().getServer().getScheduler().runTask(getPlugin(), runnable); + } + + protected final void runAsync(Runnable runnable) { + getPlugin().getServer().getScheduler().runTaskAsynchronously(getPlugin(), runnable); + } + + protected final void runTimer(Runnable runnable, long delay, long period) { + getPlugin().getServer().getScheduler().runTaskTimer(getPlugin(), runnable, delay, period); + } + + protected final void runAsyncTimer(Runnable runnable, long delay, long period) { + getPlugin().getServer().getScheduler().runTaskTimerAsynchronously(getPlugin(), runnable, delay, period); + } + + public String getName() { + return this.name; + } + + protected Gson getGson() { + return this.gson; + } + + public final void log(String message) { + log(Level.INFO, message); + } + + public final void log(Level level, String message) { + getPlugin().getLogger().log(level, String.format(LOG_FORMAT, getName(), message)); + } + + public ProjectKorra getPlugin() { + return JavaPlugin.getPlugin(ProjectKorra.class); + } +} diff --git a/src/com/projectkorra/projectkorra/module/ModuleManager.java b/src/com/projectkorra/projectkorra/module/ModuleManager.java new file mode 100644 index 00000000..20418125 --- /dev/null +++ b/src/com/projectkorra/projectkorra/module/ModuleManager.java @@ -0,0 +1,82 @@ +package com.projectkorra.projectkorra.module; + +import com.google.common.base.Preconditions; +import com.projectkorra.projectkorra.ability.AbilityManager; +import com.projectkorra.projectkorra.cooldown.CooldownManager; +import com.projectkorra.projectkorra.database.DatabaseManager; +import com.projectkorra.projectkorra.element.ElementManager; +import com.projectkorra.projectkorra.player.BendingPlayerManager; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; +import java.util.Map; + +public class ModuleManager { + + private static final Map, Module> MODULES = new HashMap<>(); + + /** + * Registers a new {@link Module} instance. + * + * @param moduleClass {@link Class} of the {@link Module} to be registered + * @throws NullPointerException if moduleClass is null + * @throws IllegalArgumentException if moduleClass has already been registered + */ + public static void registerModule(Class moduleClass) { + Preconditions.checkNotNull(moduleClass, "moduleClass cannot be null"); + Preconditions.checkArgument(!MODULES.containsKey(moduleClass), "moduleClass has already been registered"); + + try { + Constructor constructor = moduleClass.getDeclaredConstructor(); + boolean accessible = constructor.isAccessible(); + constructor.setAccessible(true); + + Module module = constructor.newInstance(); + + MODULES.put(moduleClass, module); + module.enable(); + + constructor.setAccessible(accessible); + } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + e.printStackTrace(); + } + } + + /** + * Returns a registered {@link Module} by its {@link Class}. + * + * @param moduleClass {@link Class} of the registered {@link Module} + * @return instance of the {@link Module} class + * @throws NullPointerException if moduleClass is null + * @throws IllegalArgumentException if moduleClass has not been registered + */ + public static T getModule(Class moduleClass) { + Preconditions.checkNotNull(moduleClass, "moduleClass cannot be null"); + Preconditions.checkArgument(MODULES.containsKey(moduleClass), "moduleClass has not been registered"); + + return moduleClass.cast(MODULES.get(moduleClass)); + } + + /** + * Register all our core {@link Module}s onEnable. + */ + public static void startup() { + registerModule(DatabaseManager.class); + registerModule(BendingPlayerManager.class); + registerModule(ElementManager.class); + registerModule(AbilityManager.class); + registerModule(CooldownManager.class); + } + + /** + * Disable all our core {@link Module}s onDisable. + */ + public static void shutdown() { + registerModule(CooldownManager.class); + registerModule(AbilityManager.class); + registerModule(ElementManager.class); + getModule(BendingPlayerManager.class).disable(); + getModule(DatabaseManager.class).disable(); + } +} diff --git a/src/com/projectkorra/projectkorra/player/BendingPlayer.java b/src/com/projectkorra/projectkorra/player/BendingPlayer.java new file mode 100644 index 00000000..67dae2a0 --- /dev/null +++ b/src/com/projectkorra/projectkorra/player/BendingPlayer.java @@ -0,0 +1,267 @@ +package com.projectkorra.projectkorra.player; + +import com.projectkorra.projectkorra.ability.Ability; +import com.projectkorra.projectkorra.ability.AbilityManager; +import com.projectkorra.projectkorra.ability.ChiAbility; +import com.projectkorra.projectkorra.ability.CoreAbility; +import com.projectkorra.projectkorra.ability.util.PassiveManager; +import com.projectkorra.projectkorra.cooldown.CooldownManager; +import com.projectkorra.projectkorra.element.Element; +import com.projectkorra.projectkorra.element.ElementManager; +import com.projectkorra.projectkorra.module.ModuleManager; +import org.bukkit.entity.Player; + +import java.util.*; + +public class BendingPlayer { + + private final BendingPlayerManager manager; + private final ElementManager elementManager; + private final AbilityManager abilityManager; + private final CooldownManager cooldownManager; + + private final int playerId; + private final UUID uuid; + private final Player player; + private final String playerName; + private final long firstLogin; + + private final Set elements; + private final Set toggledElements; + private final String[] abilities; + + private ChiAbility stance; + private boolean bendingRemoved; + private boolean toggled; + private boolean tremorSense; + private boolean illumination; + private boolean chiBlocked; + private long slowTime; + + public BendingPlayer(int playerId, UUID uuid, String playerName, long firstLogin) { + this.manager = ModuleManager.getModule(BendingPlayerManager.class); + this.elementManager = ModuleManager.getModule(ElementManager.class); + this.abilityManager = ModuleManager.getModule(AbilityManager.class); + this.cooldownManager = ModuleManager.getModule(CooldownManager.class); + + this.playerId = playerId; + this.uuid = uuid; + this.player = manager.getPlugin().getServer().getPlayer(uuid); + this.playerName = playerName; + this.firstLogin = firstLogin; + + this.elements = new HashSet<>(); + this.toggledElements = new HashSet<>(); + this.abilities = new String[9]; + } + + public Set getElements() { + return new HashSet<>(this.elements); + } + + public boolean addElement(Element element) { + return this.elements.add(element); + } + + public boolean removeElement(Element element) { + return this.elements.remove(element); + } + + public void clearElements() { + this.elements.clear(); + } + + public boolean hasElement(Element element) { + if (element.equals(this.elementManager.getAvatar())) { + return this.player.hasPermission("bending.avatar"); + } + + return this.elements.contains(element); + } + + public boolean canBloodbend() { + return this.elements.contains(this.elementManager.getBlood()); + } + + public boolean canUseHealing() { + return this.elements.contains(this.elementManager.getHealing()); + } + + public boolean canIcebend() { + return this.elements.contains(this.elementManager.getIce()); + } + + public boolean canPlantbend() { + return this.elements.contains(this.elementManager.getPlant()); + } + + public boolean canLavabend() { + return this.elements.contains(this.elementManager.getLava()); + } + + public boolean canMetalbend() { + return this.elements.contains(this.elementManager.getMetal()); + } + + public boolean canSandbend() { + return this.elements.contains(this.elementManager.getSand()); + } + + public boolean canCombustionbend() { + return this.elements.contains(this.elementManager.getCombustion()); + } + + public boolean canUseLightning() { + return this.elements.contains(this.elementManager.getLightning()); + } + + public boolean canUseFlight() { + return this.elements.contains(this.elementManager.getFlight()); + } + + public boolean canUseSpiritual() { + return this.elements.contains(this.elementManager.getSpiritual()); + } + + public boolean isElementToggled(Element element) { + return this.toggledElements.contains(element); + } + + public void toggleElement(Element element) { + if (this.toggledElements.contains(element)) { + this.toggledElements.remove(element); + } else { + this.toggledElements.add(element); + } + } + + public CoreAbility getBoundAbility() { + return CoreAbility.getAbility(getBoundAbilityName()); + } + + public String getBoundAbilityName() { + int slot = this.player.getInventory().getHeldItemSlot(); + return this.abilities[slot]; + } + + public String getAbility(int slot) { + return this.abilities[slot]; + } + + public List getAbilities() { + return Arrays.asList(this.abilities); + } + + public void setAbilities(String[] abilities) { + System.arraycopy(abilities, 0, this.abilities, 0, 9); + } + + public void setAbility(int slot, String abilityName) { + this.abilities[slot] = abilityName; + } + + public void addCooldown(Ability ability) { + addCooldown(ability, ability.getCooldown()); + } + + public void addCooldown(Ability ability, long duration) { + addCooldown(ability.getName(), duration); + } + + public void addCooldown(Ability ability, long duration, boolean permanent) { + addCooldown(ability.getName(), duration, permanent); + } + + public void addCooldown(String abilityName, long duration) { + addCooldown(abilityName, duration, false); + } + + public void addCooldown(String abilityName, long duration, boolean permanent) { + this.cooldownManager.addCooldown(this.player, abilityName, duration, permanent); + } + + public boolean isOnCooldown(Ability ability) { + return isOnCooldown(ability.getName()); + } + + public boolean isOnCooldown(String abilityName) { + return this.cooldownManager.isOnCooldown(this.player, abilityName); + } + + public void removeCooldown(Ability ability) { + removeCoolldown(ability.getName()); + } + + public void removeCoolldown(String abilityName) { + this.cooldownManager.removeCooldown(this.player, abilityName); + } + + public ChiAbility getStance() { + return this.stance; + } + + public void setStance(ChiAbility stance) { + this.stance = stance; + } + + public boolean isBendingRemoved() { + return this.bendingRemoved; + } + + protected void setBendingRemoved(boolean bendingRemoved) { + this.bendingRemoved = bendingRemoved; + } + + public boolean isToggled() { + return this.toggled; + } + + public void toggleBending() { + this.toggled = !this.toggled; + PassiveManager.registerPassives(this.player); // TODO redo this passive system + } + + public boolean isTremorSensing() { + return this.tremorSense; + } + + public void toggleTremorSense() { + this.tremorSense = !this.tremorSense; + } + + public boolean isIlluminating() { + return this.illumination; + } + + public void toggleIllumination() { + this.illumination = !this.illumination; + } + + public boolean isChiBlocked() { + return this.chiBlocked; + } + + public void blockChi() { + this.chiBlocked = true; + } + + public void unblockChi() { + this.chiBlocked = false; + } + + public boolean canBeSlowed() { + return System.currentTimeMillis() > this.slowTime; + } + + public void slow(long cooldown) { + this.slowTime = System.currentTimeMillis() + cooldown; + } + + public int getId() { + return this.playerId; + } + + public long getFirstLogin() { + return this.firstLogin; + } +} diff --git a/src/com/projectkorra/projectkorra/player/BendingPlayerLoadedEvent.java b/src/com/projectkorra/projectkorra/player/BendingPlayerLoadedEvent.java new file mode 100644 index 00000000..85e8deeb --- /dev/null +++ b/src/com/projectkorra/projectkorra/player/BendingPlayerLoadedEvent.java @@ -0,0 +1,31 @@ +package com.projectkorra.projectkorra.player; + +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; + +public class BendingPlayerLoadedEvent extends PlayerEvent { + + private static final HandlerList HANDLER_LIST = new HandlerList(); + + private final BendingPlayer bendingPlayer; + + public BendingPlayerLoadedEvent(Player player, BendingPlayer bendingPlayer) { + super(player); + + this.bendingPlayer = bendingPlayer; + } + + public BendingPlayer getBendingPlayer() { + return this.bendingPlayer; + } + + @Override + public HandlerList getHandlers() { + return HANDLER_LIST; + } + + public static HandlerList getHandlerList() { + return HANDLER_LIST; + } +} diff --git a/src/com/projectkorra/projectkorra/player/BendingPlayerManager.java b/src/com/projectkorra/projectkorra/player/BendingPlayerManager.java new file mode 100644 index 00000000..321dc062 --- /dev/null +++ b/src/com/projectkorra/projectkorra/player/BendingPlayerManager.java @@ -0,0 +1,119 @@ +package com.projectkorra.projectkorra.player; + +import com.projectkorra.projectkorra.GeneralMethods; +import com.projectkorra.projectkorra.event.PlayerCooldownChangeEvent; +import com.projectkorra.projectkorra.module.DatabaseModule; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.player.PlayerLoginEvent; +import org.bukkit.event.player.PlayerQuitEvent; + +import java.sql.SQLException; +import java.util.*; + +public class BendingPlayerManager extends DatabaseModule { + + private final Map players = new HashMap<>(); + + private final Set disconnected = new HashSet<>(); + private final long databaseSyncInterval = 20 * 30; + + private BendingPlayerManager() { + super("Bending Player", new BendingPlayerRepository()); + + runAsync(() -> { + try { + getRepository().createTables(); + + for (Player player : getPlugin().getServer().getOnlinePlayers()) { + loadBendingPlayer(player); + } + } catch (SQLException e) { + e.printStackTrace(); + } + }); + + runTimer(() -> { + this.disconnected.forEach(this.players::remove); + this.disconnected.clear(); + }, this.databaseSyncInterval, this.databaseSyncInterval); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onLogin(PlayerLoginEvent event) { + if (this.disconnected.remove(event.getPlayer().getUniqueId())) { + return; + } + + runAsync(() -> { + loadBendingPlayer(event.getPlayer()); + }); + } + + @EventHandler + public void onQuit(PlayerQuitEvent event) { + this.disconnected.add(event.getPlayer().getUniqueId()); + } + + @EventHandler + public void onCooldownChange(PlayerCooldownChangeEvent event) { + Player player = event.getPlayer(); + BendingPlayer bendingPlayer = this.players.get(player.getUniqueId()); + + String ability = bendingPlayer.getBoundAbilityName(); + + if (ability != null && ability.equals(event.getAbility())) { + GeneralMethods.displayMovePreview(player); + } + } + + public void removeBending(Player player) { + BendingPlayer bendingPlayer = this.players.get(player.getUniqueId()); + + bendingPlayer.setBendingRemoved(true); + + updateBendingRemoved(bendingPlayer); + } + + public void returnBending(Player player) { + BendingPlayer bendingPlayer = this.players.get(player.getUniqueId()); + + bendingPlayer.setBendingRemoved(false); + + updateBendingRemoved(bendingPlayer); + } + + private void updateBendingRemoved(BendingPlayer bendingPlayer) { + runAsync(() -> { + try { + getRepository().updateBendingRemoved(bendingPlayer); + } catch (SQLException e) { + e.printStackTrace(); + } + }); + } + + private void loadBendingPlayer(Player player) { + try { + BendingPlayer bendingPlayer = getRepository().selectPlayer(player); + + runSync(() -> { + this.players.put(player.getUniqueId(), bendingPlayer); + + BendingPlayerLoadedEvent bendingPlayerLoadedEvent = new BendingPlayerLoadedEvent(player, bendingPlayer); + getPlugin().getServer().getPluginManager().callEvent(bendingPlayerLoadedEvent); + }); + } catch (SQLException e) { + e.printStackTrace(); + } + } + + public BendingPlayer getBendingPlayer(Player player) { + return getBendingPlayer(player.getUniqueId()); + } + + public BendingPlayer getBendingPlayer(UUID uuid) { + return this.players.get(uuid); + } +} diff --git a/src/com/projectkorra/projectkorra/player/BendingPlayerRepository.java b/src/com/projectkorra/projectkorra/player/BendingPlayerRepository.java new file mode 100644 index 00000000..e59ac602 --- /dev/null +++ b/src/com/projectkorra/projectkorra/player/BendingPlayerRepository.java @@ -0,0 +1,118 @@ +package com.projectkorra.projectkorra.player; + +import com.projectkorra.projectkorra.database.DatabaseQuery; +import com.projectkorra.projectkorra.database.DatabaseRepository; +import org.bukkit.entity.Player; + +import java.nio.ByteBuffer; +import java.sql.*; +import java.util.UUID; + +public class BendingPlayerRepository extends DatabaseRepository { + + private static final DatabaseQuery CREATE_TABLE_BENDING_PLAYERS = DatabaseQuery.newBuilder() + .mysql("CREATE TABLE IF NOT EXISTS pk_bending_players (player_id INTEGER PRIMARY KEY AUTO_INCREMENT, uuid BINARY(16) NOT NULL, player_name VARCHAR(16) NOT NULL, first_login BIGINT NOT NULL, bending_removed BOOLEAN, INDEX uuid_index (uuid));") + .sqlite("CREATE TABLE IF NOT EXISTS pk_bending_players (player_id INTEGER PRIMARY KEY AUTOINCREMENT, uuid BINARY(16) NOT NULL, player_name VARCHAR(16) NOT NULL, first_login BIGINT NOT NULL, bending_removed BOOLEAN); CREATE INDEX uuid_index ON pk_bending_players (uuid);") + .build(); + + private static final DatabaseQuery SELECT_BENDING_PLAYER = DatabaseQuery.newBuilder() + .query("SELECT player_id, player_name, first_login, bending_removed FROM pk_bending_players WHERE uuid = ?;") + .build(); + + private static final DatabaseQuery INSERT_BENDING_PLAYER = DatabaseQuery.newBuilder() + .query("INSERT INTO pk_bending_players (uuid, player_name, first_login) VALUES (?, ?, ?);") + .build(); + + private static final DatabaseQuery UPDATE_PLAYER_NAME = DatabaseQuery.newBuilder() + .query("UPDATE pk_bending_players SET player_name = ? WHERE player_id = ?;") + .build(); + + private static final DatabaseQuery UPDATE_BENDING_REMOVED = DatabaseQuery.newBuilder() + .query("UPDATE pk_bending_players SET bending_removed = ? WHERE player_id = ?;") + .build(); + + protected void createTables() throws SQLException { + Connection connection = getDatabase().getConnection(); + + try (PreparedStatement statement = connection.prepareStatement(CREATE_TABLE_BENDING_PLAYERS.getQuery())) { + statement.executeUpdate(); + } + } + + protected BendingPlayer selectPlayer(Player player) throws SQLException { + UUID uuid = player.getUniqueId(); + byte[] binaryUUID = ByteBuffer.allocate(16).putLong(uuid.getMostSignificantBits()).putLong(uuid.getLeastSignificantBits()).array(); + + Connection connection = getDatabase().getConnection(); + + try (PreparedStatement statement = connection.prepareStatement(SELECT_BENDING_PLAYER.getQuery())) { + statement.setBytes(1, binaryUUID); + + try (ResultSet rs = statement.executeQuery()) { + if (!rs.next()) { + return insertPlayer(player.getUniqueId(), player.getName()); + } + + int playerId = rs.getInt("player_id"); + String playerName = rs.getString("player_name"); + long firstLogin = rs.getLong("first_login"); + boolean bendingRemoved = rs.getBoolean("bending_removed"); + + if (!player.getName().equals(playerName)) { + updatePlayerName(playerId, player.getName()); + } + + BendingPlayer bendingPlayer = new BendingPlayer(playerId, uuid, playerName, firstLogin); + + bendingPlayer.setBendingRemoved(bendingRemoved); + + return bendingPlayer; + } + } + } + + private BendingPlayer insertPlayer(UUID uuid, String playerName) throws SQLException { + byte[] binaryUUID = ByteBuffer.allocate(16).putLong(uuid.getMostSignificantBits()).putLong(uuid.getLeastSignificantBits()).array(); + + Connection connection = getDatabase().getConnection(); + long firstLogin = System.currentTimeMillis(); + + try (PreparedStatement statement = connection.prepareStatement(INSERT_BENDING_PLAYER.getQuery(), Statement.RETURN_GENERATED_KEYS)) { + statement.setBytes(1, binaryUUID); + statement.setString(2, playerName); + statement.setLong(3, firstLogin); + + statement.executeUpdate(); + + try (ResultSet rs = statement.getGeneratedKeys()) { + rs.next(); + + int playerId = rs.getInt(1); + + return new BendingPlayer(playerId, uuid, playerName, firstLogin); + } + } + } + + protected void updatePlayerName(int playerId, String playerName) throws SQLException { + Connection connection = getDatabase().getConnection(); + + try (PreparedStatement statement = connection.prepareStatement(UPDATE_PLAYER_NAME.getQuery())) { + statement.setInt(1, playerId); + statement.setString(2, playerName); + + statement.executeUpdate(); + } + } + + protected void updateBendingRemoved(BendingPlayer bendingPlayer) throws SQLException { + Connection connection = getDatabase().getConnection(); + + try (PreparedStatement statement = connection.prepareStatement(UPDATE_BENDING_REMOVED.getQuery())) { + statement.setInt(1, bendingPlayer.getId()); + statement.setBoolean(2, bendingPlayer.isBendingRemoved()); + + statement.executeUpdate(); + } + } +} diff --git a/src/com/projectkorra/projectkorra/waterbending/SurgeWall.java b/src/com/projectkorra/projectkorra/waterbending/SurgeWall.java index b451f74a..7db0060e 100644 --- a/src/com/projectkorra/projectkorra/waterbending/SurgeWall.java +++ b/src/com/projectkorra/projectkorra/waterbending/SurgeWall.java @@ -256,12 +256,11 @@ public class SurgeWall extends WaterAbility { } final ArrayList blocks = new ArrayList(); - final Location targetLoc = GeneralMethods.getTargetedLocation(this.player, (int) this.range, Material.WATER, Material.ICE); + final Location targetLoc = GeneralMethods.getTargetedLocation(this.player, (int) this.range, false, false, Material.WATER, Material.ICE); this.location = targetLoc.clone(); final Vector eyeDir = this.player.getEyeLocation().getDirection(); Vector vector; Block block; - for (double i = 0; i <= this.getNightFactor(this.radius); i += 0.5) { for (double angle = 0; angle < 360; angle += 10) { vector = GeneralMethods.getOrthogonalVector(eyeDir.clone(), angle, i); diff --git a/src/com/projectkorra/projectkorra/waterbending/util/WaterReturn.java b/src/com/projectkorra/projectkorra/waterbending/util/WaterReturn.java index 52dd2c28..15543946 100644 --- a/src/com/projectkorra/projectkorra/waterbending/util/WaterReturn.java +++ b/src/com/projectkorra/projectkorra/waterbending/util/WaterReturn.java @@ -116,8 +116,8 @@ public class WaterReturn extends WaterAbility { private void fillBottle() { final PlayerInventory inventory = this.player.getInventory(); - if (inventory.contains(Material.GLASS_BOTTLE)) { - final int index = inventory.first(Material.GLASS_BOTTLE); + final int index = inventory.first(Material.GLASS_BOTTLE); + if (index >= 0) { final ItemStack item = inventory.getItem(index); final ItemStack water = waterBottleItem(); @@ -148,27 +148,15 @@ public class WaterReturn extends WaterAbility { return false; } - public static boolean hasWaterBottle(final Player player) { - if (hasAbility(player, WaterReturn.class) || isBending(player)) { - return false; - } - final PlayerInventory inventory = player.getInventory(); - if (inventory.contains(Material.POTION)) { - final ItemStack item = inventory.getItem(inventory.first(Material.POTION)); - final PotionMeta meta = (PotionMeta) item.getItemMeta(); - return meta.getBasePotionData().getType() == PotionType.WATER; - } - return false; - } - - public static void emptyWaterBottle(final Player player) { - final PlayerInventory inventory = player.getInventory(); + public static int firstWaterBottle(final PlayerInventory inventory) { int index = inventory.first(Material.POTION); // Check that the first one found is actually a WATER bottle. We aren't implementing potion bending just yet. - if (index != -1 && !((PotionMeta) inventory.getItem(index).getItemMeta()).getBasePotionData().getType().equals(PotionType.WATER)) { - for (int i = 0; i < inventory.getSize(); i++) { - if (inventory.getItem(i).getType() == Material.POTION) { + if (index != -1) { + int aux = index; + index = -1; + for (int i = aux; i < inventory.getSize(); i++) { + if (inventory.getItem(i) != null && inventory.getItem(i).getType() == Material.POTION && inventory.getItem(i).hasItemMeta()) { final PotionMeta meta = (PotionMeta) inventory.getItem(i).getItemMeta(); if (meta.getBasePotionData().getType().equals(PotionType.WATER)) { index = i; @@ -178,6 +166,22 @@ public class WaterReturn extends WaterAbility { } } + return index; + } + + public static boolean hasWaterBottle(final Player player) { + if (hasAbility(player, WaterReturn.class) || isBending(player)) { + return false; + } + final PlayerInventory inventory = player.getInventory(); + + return WaterReturn.firstWaterBottle(inventory) >= 0; + } + + public static void emptyWaterBottle(final Player player) { + final PlayerInventory inventory = player.getInventory(); + int index = WaterReturn.firstWaterBottle(inventory); + if (index != -1) { final ItemStack item = inventory.getItem(index); if (item.getAmount() == 1) {