diff --git a/config.yml b/config.yml index 33d5a52..7d37126 100644 --- a/config.yml +++ b/config.yml @@ -6,6 +6,10 @@ Health Message: '&7&l{name}: {usestyle}' +# The message the player is sent to the player if they have actionhealth disabled. +# {name} shows the name of the player. +Toggle Message: '' + # Strip Name. Strip Name: true @@ -51,4 +55,12 @@ Region PvP: true Limit Health: true # Saves players /actionhealth toggle state. -Remember Toggle: false \ No newline at end of file +Remember Toggle: false + +# Blacklist by entity name or entity display name. +Blacklist: +- 'CCPD Officer' + +# Show the health of the entity that the player is looking at. +Show On Look: false +Look Distance: 10 \ No newline at end of file diff --git a/out/production/ActionHealth/com/zeshanaslam/actionhealth/HealthListeners.class b/out/production/ActionHealth/com/zeshanaslam/actionhealth/HealthListeners.class index edfd2af..40bf094 100644 Binary files a/out/production/ActionHealth/com/zeshanaslam/actionhealth/HealthListeners.class and b/out/production/ActionHealth/com/zeshanaslam/actionhealth/HealthListeners.class differ diff --git a/out/production/ActionHealth/com/zeshanaslam/actionhealth/HealthUtil$1.class b/out/production/ActionHealth/com/zeshanaslam/actionhealth/HealthUtil$1.class index f3c4e1d..dea40fc 100644 Binary files a/out/production/ActionHealth/com/zeshanaslam/actionhealth/HealthUtil$1.class and b/out/production/ActionHealth/com/zeshanaslam/actionhealth/HealthUtil$1.class differ diff --git a/out/production/ActionHealth/com/zeshanaslam/actionhealth/HealthUtil.class b/out/production/ActionHealth/com/zeshanaslam/actionhealth/HealthUtil.class index b22d536..5871788 100644 Binary files a/out/production/ActionHealth/com/zeshanaslam/actionhealth/HealthUtil.class and b/out/production/ActionHealth/com/zeshanaslam/actionhealth/HealthUtil.class differ diff --git a/out/production/ActionHealth/com/zeshanaslam/actionhealth/LookThread.class b/out/production/ActionHealth/com/zeshanaslam/actionhealth/LookThread.class new file mode 100644 index 0000000..bf6e84e Binary files /dev/null and b/out/production/ActionHealth/com/zeshanaslam/actionhealth/LookThread.class differ diff --git a/out/production/ActionHealth/com/zeshanaslam/actionhealth/Main.class b/out/production/ActionHealth/com/zeshanaslam/actionhealth/Main.class index c0ac8d0..155f9a2 100644 Binary files a/out/production/ActionHealth/com/zeshanaslam/actionhealth/Main.class and b/out/production/ActionHealth/com/zeshanaslam/actionhealth/Main.class differ diff --git a/out/production/ActionHealth/com/zeshanaslam/actionhealth/SettingsManager.class b/out/production/ActionHealth/com/zeshanaslam/actionhealth/SettingsManager.class index 43e2165..d7f1d5f 100644 Binary files a/out/production/ActionHealth/com/zeshanaslam/actionhealth/SettingsManager.class and b/out/production/ActionHealth/com/zeshanaslam/actionhealth/SettingsManager.class differ diff --git a/out/production/ActionHealth/com/zeshanaslam/actionhealth/TargetHelper.class b/out/production/ActionHealth/com/zeshanaslam/actionhealth/TargetHelper.class new file mode 100644 index 0000000..2f2f47c Binary files /dev/null and b/out/production/ActionHealth/com/zeshanaslam/actionhealth/TargetHelper.class differ diff --git a/plugin.yml b/plugin.yml index d1b8795..e9cd788 100644 --- a/plugin.yml +++ b/plugin.yml @@ -1,6 +1,6 @@ name: ActionHealth main: com.zeshanaslam.actionhealth.Main -version: 3.1.3 +version: 3.1.4 commands: Actionhealth: description: Actionhealth main command. \ No newline at end of file diff --git a/src/com/zeshanaslam/actionhealth/HealthListeners.java b/src/com/zeshanaslam/actionhealth/HealthListeners.java index e8603ea..b6b90ef 100644 --- a/src/com/zeshanaslam/actionhealth/HealthListeners.java +++ b/src/com/zeshanaslam/actionhealth/HealthListeners.java @@ -39,6 +39,9 @@ public class HealthListeners implements Listener { Entity damaged = event.getEntity(); + if (event.getDamager().getUniqueId() == damaged.getUniqueId()) { + return; + } if (event.getDamager() instanceof Projectile) { Projectile projectile = (Projectile) event.getDamager(); @@ -58,13 +61,16 @@ public class HealthListeners implements Listener { } if (plugin.toggle.contains(player.getUniqueId())) { + if (plugin.settingsManager.toggleMessage != null && !plugin.settingsManager.toggleMessage.equals("")) { + plugin.healthUtil.sendActionBar(player, plugin.settingsManager.toggleMessage.replace("{name}", player.getName())); + } return; } // Send health if (damaged instanceof LivingEntity) { LivingEntity livingEntity = (LivingEntity) damaged; - plugin.healthUtil.sendHealth(player, (LivingEntity) damaged, livingEntity.getHealth() - event.getFinalDamage()); + plugin.healthUtil.sendHealth(player, livingEntity, livingEntity.getHealth() - event.getFinalDamage()); } } } @@ -94,12 +100,12 @@ public class HealthListeners implements Listener { // Send health if (damaged instanceof LivingEntity) { LivingEntity livingEntity = (LivingEntity) damaged; - plugin.healthUtil.sendHealth(player, (LivingEntity) damaged, livingEntity.getHealth() - event.getFinalDamage()); + plugin.healthUtil.sendHealth(player, livingEntity, livingEntity.getHealth() - event.getFinalDamage()); } } } - @EventHandler + @EventHandler(priority = EventPriority.MONITOR) public void onJoin(PlayerJoinEvent event) { Player player = event.getPlayer(); @@ -112,7 +118,7 @@ public class HealthListeners implements Listener { } } - @EventHandler + @EventHandler(priority = EventPriority.MONITOR) public void onLeave(PlayerQuitEvent event) { Player player = event.getPlayer(); diff --git a/src/com/zeshanaslam/actionhealth/HealthUtil.java b/src/com/zeshanaslam/actionhealth/HealthUtil.java index 2006704..0cc5412 100644 --- a/src/com/zeshanaslam/actionhealth/HealthUtil.java +++ b/src/com/zeshanaslam/actionhealth/HealthUtil.java @@ -24,11 +24,17 @@ public class HealthUtil { new BukkitRunnable() { public void run() { - sendActionBar(player, getOutput((int) entity.getHealth(), entity)); + String output = getOutput(entity.getHealth(), entity); + + if (output != null) + sendActionBar(player, output); } }.runTaskLater(plugin, 1L); } else { - sendActionBar(player, getOutput(health, entity)); + String output = getOutput(health, entity); + + if (output != null) + sendActionBar(player, output); } } @@ -44,6 +50,8 @@ public class HealthUtil { name = entity.getCustomName(); } + if (plugin.settingsManager.blacklist.contains(name)) return null; + if (plugin.settingsManager.stripName) name = ChatColor.stripColor(name); if (plugin.settingsManager.translate.containsKey(entity.getName())) name = plugin.settingsManager.translate.get(entity.getName()); @@ -81,8 +89,14 @@ public class HealthUtil { } } - for (int i = 0; i < left; i++) { - style = style + plugin.settingsManager.emptyHeartIcon; + if (maxHealth != health) { + for (int i = 0; i < left; i++) { + style = style + plugin.settingsManager.emptyHeartIcon; + } + } else { + for (int i = 0; i < left; i++) { + style = style + plugin.settingsManager.filledHeartIcon; + } } output = output.replace("{usestyle}", style); @@ -91,7 +105,7 @@ public class HealthUtil { return output; } - private void sendActionBar(Player player, String message) { + public void sendActionBar(Player player, String message) { message = ChatColor.translateAlternateColorCodes('&', message); try { diff --git a/src/com/zeshanaslam/actionhealth/LookThread.java b/src/com/zeshanaslam/actionhealth/LookThread.java new file mode 100644 index 0000000..903c672 --- /dev/null +++ b/src/com/zeshanaslam/actionhealth/LookThread.java @@ -0,0 +1,46 @@ +package com.zeshanaslam.actionhealth; + +import org.bukkit.Bukkit; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.List; + +public class LookThread extends BukkitRunnable { + + private Main plugin; + + public LookThread(Main plugin) { + this.plugin = plugin; + } + + @Override + public void run() { + for (Player player : Bukkit.getOnlinePlayers()) { + if (plugin.toggle.contains(player.getUniqueId())) { + if (plugin.settingsManager.toggleMessage != null && !plugin.settingsManager.toggleMessage.equals("")) { + plugin.healthUtil.sendActionBar(player, plugin.settingsManager.toggleMessage.replace("{name}", player.getName())); + } + continue; + } + + List entities = TargetHelper.getLivingTargets(player, plugin.settingsManager.lookDistance); + + if (!entities.isEmpty()) { + LivingEntity livingEntity = entities.get(0); + String name; + + if (livingEntity.getCustomName() == null) { + name = livingEntity.getName(); + } else { + name = livingEntity.getCustomName(); + } + + if (!plugin.settingsManager.blacklist.contains(name)) { + plugin.healthUtil.sendHealth(player, livingEntity, livingEntity.getHealth()); + } + } + } + } +} diff --git a/src/com/zeshanaslam/actionhealth/Main.java b/src/com/zeshanaslam/actionhealth/Main.java index ac8a282..7de17cd 100644 --- a/src/com/zeshanaslam/actionhealth/Main.java +++ b/src/com/zeshanaslam/actionhealth/Main.java @@ -14,6 +14,7 @@ public class Main extends JavaPlugin { public SettingsManager settingsManager; public WorldGuardPlugin worldGuardPlugin; public HealthUtil healthUtil; + public int taskID = -1; public List toggle = new ArrayList<>(); @@ -33,7 +34,7 @@ public class Main extends JavaPlugin { // Register listeners getServer().getPluginManager().registerEvents(new HealthListeners(this), this); - + // Register command getCommand("Actionhealth").setExecutor(new HealthCommand(this)); diff --git a/src/com/zeshanaslam/actionhealth/SettingsManager.java b/src/com/zeshanaslam/actionhealth/SettingsManager.java index 3594587..3b2d685 100644 --- a/src/com/zeshanaslam/actionhealth/SettingsManager.java +++ b/src/com/zeshanaslam/actionhealth/SettingsManager.java @@ -1,10 +1,13 @@ package com.zeshanaslam.actionhealth; import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.scheduler.BukkitTask; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.stream.Collectors; public class SettingsManager { @@ -22,11 +25,22 @@ public class SettingsManager { public List worlds = new ArrayList<>(); public HashMap translate = new HashMap<>(); public List regions = new ArrayList<>(); - public String mcVersion; public boolean useOldMethods; + public boolean showOnLook; + public double lookDistance; + public List blacklist = new ArrayList<>(); + public String toggleMessage; public SettingsManager(Main plugin) { + // Clear settings for reloads + worlds.clear(); + regions.clear(); + blacklist.clear(); + + if (plugin.taskID != -1) Bukkit.getScheduler().cancelTask(plugin.taskID); + + // Get settings from config healthMessage = plugin.getConfig().getString("Health Message"); usePerms = plugin.getConfig().getBoolean("Use Permissions"); showMobs = plugin.getConfig().getBoolean("Show Mob"); @@ -60,5 +74,27 @@ public class SettingsManager { } else { rememberToggle = false; } + + // New options + if (plugin.getConfig().contains("Blacklist")) { + blacklist.addAll(plugin.getConfig().getStringList("Blacklist").stream().map(s -> ChatColor.translateAlternateColorCodes('&', s)).collect(Collectors.toList())); + } + + if (plugin.getConfig().contains("Show On Look")) { + showOnLook = plugin.getConfig().getBoolean("Show On Look"); + lookDistance = plugin.getConfig().getDouble("Look Distance"); + + if (showOnLook) { + BukkitTask bukkitTask = new LookThread(plugin).runTaskTimerAsynchronously(plugin, 0, 20); + plugin.taskID = bukkitTask.getTaskId(); + } + } else { + plugin.taskID = -1; + showOnLook = false; + } + + if (plugin.getConfig().contains("Toggle Message")) { + toggleMessage = plugin.getConfig().getString("Toggle Message"); + } } } diff --git a/src/com/zeshanaslam/actionhealth/TargetHelper.java b/src/com/zeshanaslam/actionhealth/TargetHelper.java new file mode 100644 index 0000000..80f105b --- /dev/null +++ b/src/com/zeshanaslam/actionhealth/TargetHelper.java @@ -0,0 +1,288 @@ +package com.zeshanaslam.actionhealth; + +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.util.Vector; + +import java.util.ArrayList; +import java.util.List; + +/** + *

Helper class for getting targets using various methods

+ */ +public class TargetHelper { + + /** + *

Number of pixels that end up displaying about 1 degree of vision in the client window

+ *

Not really useful since you can't get the client's window size, but I added it in case + * it becomes useful sometime

+ */ + private static final int PIXELS_PER_DEGREE = 35; + + /** + *

Gets all entities the player is looking at within the range

+ *

Has a little bit of tolerance to make targeting easier

+ * + * @param source living entity to get the targets of + * @param range maximum range to check + * @return all entities in the player's vision line + */ + public static List getLivingTargets(LivingEntity source, double range) { + return getLivingTargets(source, range, 4); + } + + /** + *

Gets all entities the player is looking at within the range using + * the given tolerance.

+ * + * @param source living entity to get the targets of + * @param range maximum range to check + * @param tolerance tolerance of the line calculation + * @return all entities in the player's vision line + */ + public static List getLivingTargets(LivingEntity source, double range, double tolerance) { + List list = source.getNearbyEntities(range, range, range); + List targets = new ArrayList(); + + Vector facing = source.getLocation().getDirection(); + double fLengthSq = facing.lengthSquared(); + + for (Entity entity : list) { + if (!isInFront(source, entity) || !(entity instanceof LivingEntity)) continue; + + Vector relative = entity.getLocation().subtract(source.getLocation()).toVector(); + double dot = relative.dot(facing); + double rLengthSq = relative.lengthSquared(); + double cosSquared = (dot * dot) / (rLengthSq * fLengthSq); + double sinSquared = 1 - cosSquared; + double dSquared = rLengthSq * sinSquared; + + // If close enough to vision line, return the entity + if (dSquared < tolerance) targets.add((LivingEntity) entity); + } + + return targets; + } + + /** + *

Gets the entity the player is looking at

+ *

Has a little bit of tolerance to make targeting easier

+ * + * @param source living entity to get the target of + * @param range maximum range to check + * @return entity player is looing at or null if not found + */ + public static LivingEntity getLivingTarget(LivingEntity source, double range) { + return getLivingTarget(source, range, 4); + } + + /** + *

Gets the entity the player is looking at

+ *

Has a little bit of tolerance to make targeting easier

+ * + * @param source living entity to get the target of + * @param range maximum range to check + * @param tolerance tolerance of the line calculation + * @return entity player is looking at or null if not found + */ + public static LivingEntity getLivingTarget(LivingEntity source, double range, double tolerance) { + List targets = getLivingTargets(source, range, tolerance); + if (targets.size() == 0) return null; + LivingEntity target = targets.get(0); + double minDistance = target.getLocation().distanceSquared(source.getLocation()); + for (LivingEntity entity : targets) { + double distance = entity.getLocation().distanceSquared(source.getLocation()); + if (distance < minDistance) { + minDistance = distance; + target = entity; + } + } + return target; + } + + /** + * Gets the targets in a cone + * + * @param source entity to get the targets for + * @param arc arc angle of the cone + * @param range range of the cone + * @return list of targets + */ + public static List getConeTargets(LivingEntity source, double arc, double range) { + List targets = new ArrayList(); + List list = source.getNearbyEntities(range, range, range); + if (arc <= 0) return targets; + + // Initialize values + Vector dir = source.getLocation().getDirection(); + dir.setY(0); + double cos = Math.cos(arc * Math.PI / 180); + double cosSq = cos * cos; + + // Get the targets in the cone + for (Entity entity : list) { + if (entity instanceof LivingEntity) { + + // Greater than 360 degrees is all targets + if (arc >= 360) { + targets.add((LivingEntity) entity); + } + + // Otherwise, select targets based on dot product + else { + Vector relative = entity.getLocation().subtract(source.getLocation()).toVector(); + relative.setY(0); + double dot = relative.dot(dir); + double value = dot * dot / relative.lengthSquared(); + if (arc < 180 && dot > 0 && value >= cosSq) targets.add((LivingEntity) entity); + else if (arc >= 180 && (dot > 0 || dot <= cosSq)) targets.add((LivingEntity) entity); + } + } + } + + return targets; + } + + /** + * Checks if the entity is in front of the entity + * + * @param entity entity to check for + * @param target target to check against + * @return true if the target is in front of the entity + */ + public static boolean isInFront(Entity entity, Entity target) { + + // Get the necessary vectors + Vector facing = entity.getLocation().getDirection(); + Vector relative = target.getLocation().subtract(entity.getLocation()).toVector(); + + // If the dot product is positive, the target is in front + return facing.dot(relative) >= 0; + } + + /** + * Checks if the entity is in front of the entity restricted to the given angle + * + * @param entity entity to check for + * @param target target to check against + * @param angle angle to restrict it to (0-360) + * @return true if the target is in front of the entity + */ + public static boolean isInFront(Entity entity, Entity target, double angle) { + if (angle <= 0) return false; + if (angle >= 360) return true; + + // Get the necessary data + double dotTarget = Math.cos(angle); + Vector facing = entity.getLocation().getDirection(); + Vector relative = target.getLocation().subtract(entity.getLocation()).toVector().normalize(); + + // Compare the target dot product with the actual result + return facing.dot(relative) >= dotTarget; + } + + /** + * Checks if the target is behind the entity + * + * @param entity entity to check for + * @param target target to check against + * @return true if the target is behind the entity + */ + public static boolean isBehind(Entity entity, Entity target) { + return !isInFront(entity, target); + } + + /** + * Checks if the entity is behind the player restricted to the given angle + * + * @param entity entity to check for + * @param target target to check against + * @param angle angle to restrict it to (0-360) + * @return true if the target is behind the entity + */ + public static boolean isBehind(Entity entity, Entity target, double angle) { + if (angle <= 0) return false; + if (angle >= 360) return true; + + // Get the necessary data + double dotTarget = Math.cos(angle); + Vector facing = entity.getLocation().getDirection(); + Vector relative = entity.getLocation().subtract(target.getLocation()).toVector().normalize(); + + // Compare the target dot product and the actual result + return facing.dot(relative) >= dotTarget; + } + + /** + * Checks whether or not the line between the two points is obstructed + * + * @param loc1 first location + * @param loc2 second location + * @return the location of obstruction or null if not obstructed + */ + public static boolean isObstructed(Location loc1, Location loc2) { + if (loc1.getX() == loc2.getX() && loc1.getY() == loc2.getY() && loc1.getZ() == loc2.getZ()) { + return false; + } + Vector slope = loc2.clone().subtract(loc1).toVector(); + int steps = (int) (slope.length() * 4) + 1; + slope.multiply(1.0 / steps); + Location temp = loc1.clone(); + for (int i = 0; i < steps; i++) { + temp.add(slope); + if (temp.getBlock().getType().isSolid() && temp.getBlock().getType() != Material.IRON_FENCE && !temp.getBlock().getType().toString().contains("GLASS")) { + return true; + } + } + return false; + } + + /** + * Retrieves an open location along the line for teleporting or linear targeting + * + * @param loc1 start location of the path + * @param loc2 end location of the path + * @param throughWall whether or not going through walls is allowed + * @return the farthest open location along the path + */ + public static Location getOpenLocation(Location loc1, Location loc2, boolean throughWall) { + // Special case + if (loc1.getX() == loc2.getX() && loc1.getY() == loc2.getY() && loc1.getZ() == loc2.getZ()) { + return loc1; + } + + // Common data + Vector slope = loc2.clone().subtract(loc1).toVector(); + int steps = (int) (slope.length() * 4) + 1; + slope.multiply(1.0 / steps); + + // Going through walls starts at the end and traverses backwards + if (throughWall) { + Location temp = loc2.clone(); + while (temp.getBlock().getType().isSolid() && steps > 0) { + temp.subtract(slope); + steps--; + } + temp.setX(temp.getBlockX() + 0.5); + temp.setZ(temp.getBlockZ() + 0.5); + temp.setY(temp.getBlockY() + 1); + return temp; + } + + // Not going through walls starts at the beginning and traverses forward + else { + Location temp = loc1.clone(); + while (!temp.getBlock().getType().isSolid() && steps > 0) { + temp.add(slope); + steps--; + } + temp.subtract(slope); + temp.setX(temp.getBlockX() + 0.5); + temp.setZ(temp.getBlockZ() + 0.5); + temp.setY(temp.getBlockY() + 1); + return temp; + } + } +} \ No newline at end of file