diff --git a/src/main/java/net/countercraft/movecraft/combat/features/directors/AADirectors.java b/src/main/java/net/countercraft/movecraft/combat/features/directors/AADirectors.java index 084bb45a..a40fc380 100644 --- a/src/main/java/net/countercraft/movecraft/combat/features/directors/AADirectors.java +++ b/src/main/java/net/countercraft/movecraft/combat/features/directors/AADirectors.java @@ -27,12 +27,16 @@ import org.bukkit.util.Vector; import org.jetbrains.annotations.NotNull; +import java.util.HashSet; +import java.util.Set; + import static net.countercraft.movecraft.util.ChatUtils.ERROR_PREFIX; public class AADirectors extends Directors implements Listener { public static final NamespacedKey ALLOW_AA_DIRECTOR_SIGN = new NamespacedKey("movecraft-combat", "allow_aa_director_sign"); private static final String HEADER = "AA Director"; public static int AADirectorDistance = 50; + public static int AADirectorNodeDistance = 3; public static int AADirectorRange = 120; private long lastCheck = 0; @@ -46,6 +50,7 @@ public static void register() { public static void load(@NotNull FileConfiguration config) { AADirectorDistance = config.getInt("AADirectorDistance", 50); + AADirectorNodeDistance = config.getInt("AADirectorNodeDistance", 3); AADirectorRange = config.getInt("AADirectorRange", 120); } @@ -75,28 +80,45 @@ private void processFireball(@NotNull SmallFireball fireball) { if (!(c instanceof PlayerCraft) || !hasDirector((PlayerCraft) c)) return; - Player p = getDirector((PlayerCraft) c); + HashSet craftDirectors = getCraftDirectors((PlayerCraft) c); + Player player; + Player dominantPlayer = null; - MovecraftLocation midPoint = c.getHitBox().getMidPoint(); - int distX = Math.abs(midPoint.getX() - fireball.getLocation().getBlockX()); - int distY = Math.abs(midPoint.getY() - fireball.getLocation().getBlockY()); - int distZ = Math.abs(midPoint.getZ() - fireball.getLocation().getBlockZ()); - if (distX > AADirectorDistance || distY > AADirectorDistance || distZ > AADirectorDistance) - return; + for (DirectorData data : craftDirectors) { + if (data.getSelectedNodes().isEmpty() || data.getSignLocations().isEmpty()) { + dominantPlayer = data.getPlayer(); + } + } + if (dominantPlayer != null) { + player = dominantPlayer; + } else { + player = getClosestDirectorFromProjectile( + craftDirectors, + fireball.getLocation().toVector(), + AADirectorNodeDistance + ); + } - fireball.setShooter(p); + if (player == null || player.getInventory().getItemInMainHand().getType() != Directors.DirectorTool) + return; - if (p == null || p.getInventory().getItemInMainHand().getType() != Directors.DirectorTool) + MovecraftLocation midpoint = c.getHitBox().getMidPoint(); + int distX = Math.abs(midpoint.getX() - fireball.getLocation().getBlockX()); + int distY = Math.abs(midpoint.getY() - fireball.getLocation().getBlockY()); + int distZ = Math.abs(midpoint.getZ() - fireball.getLocation().getBlockZ()); + if (distX*distX + distY*distY + distZ*distZ >= AADirectorDistance*AADirectorDistance) return; + fireball.setShooter(player); + Vector fireballVector = fireball.getVelocity(); double speed = fireballVector.length(); // store the speed to add it back in later, since all the values we will be using are "normalized", IE: have a speed of 1 fireballVector = fireballVector.normalize(); // you normalize it for comparison with the new direction to see if we are trying to steer too far - Block targetBlock = DirectorUtils.getDirectorBlock(p, AADirectorRange); + Block targetBlock = DirectorUtils.getDirectorBlock(player, AADirectorRange); Vector targetVector; if (targetBlock == null || targetBlock.getType().equals(Material.AIR)) // the player is looking at nothing, shoot in that general direction - targetVector = p.getLocation().getDirection(); + targetVector = player.getLocation().getDirection(); else { // shoot directly at the block the player is looking at (IE: with convergence) targetVector = targetBlock.getLocation().toVector().subtract(fireball.getLocation().toVector()); targetVector = targetVector.normalize(); @@ -177,8 +199,15 @@ public void onSignClick(@NotNull PlayerInteractEvent e) { return; } + Set selectedLines = processSign(sign); + if (isNodesShared(selectedLines, foundCraft, p)) { + p.sendMessage(ERROR_PREFIX + " " + I18nSupport.getInternationalisedString("AADirector - Must Not Share Nodes")); + return; + } + clearDirector(p); - addDirector(foundCraft, p); + addDirector(p, foundCraft, selectedLines); + p.sendMessage(I18nSupport.getInternationalisedString("AADirector - Directing")); } } diff --git a/src/main/java/net/countercraft/movecraft/combat/features/directors/CannonDirectors.java b/src/main/java/net/countercraft/movecraft/combat/features/directors/CannonDirectors.java index 3d05d0d9..0fc8610e 100644 --- a/src/main/java/net/countercraft/movecraft/combat/features/directors/CannonDirectors.java +++ b/src/main/java/net/countercraft/movecraft/combat/features/directors/CannonDirectors.java @@ -14,11 +14,13 @@ import net.countercraft.movecraft.util.MathUtils; import org.bukkit.Bukkit; import org.bukkit.ChatColor; +import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.block.Sign; +import org.bukkit.block.data.type.TNT; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.entity.Player; import org.bukkit.entity.TNTPrimed; @@ -33,7 +35,9 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.UUID; import static net.countercraft.movecraft.util.ChatUtils.ERROR_PREFIX; @@ -42,11 +46,11 @@ public class CannonDirectors extends Directors implements Listener { public static final NamespacedKey ALLOW_CANNON_DIRECTOR_SIGN = new NamespacedKey("movecraft-combat", "allow_cannon_director_sign"); private static final String HEADER = "Cannon Director"; public static int CannonDirectorDistance = 100; + public static int CannonDirectorNodeDistance = 3; public static int CannonDirectorRange = 120; private final Object2DoubleOpenHashMap tracking = new Object2DoubleOpenHashMap<>(); private long lastCheck = 0; - public CannonDirectors() { super(); } @@ -57,6 +61,7 @@ public static void register() { public static void load(@NotNull FileConfiguration config) { CannonDirectorDistance = config.getInt("CannonDirectorDistance", 100); + CannonDirectorNodeDistance = config.getInt("CannonDirectorNodeDistance", 3); CannonDirectorRange = config.getInt("CannonDirectorRange", 120); } @@ -98,16 +103,33 @@ private void processTNT(@NotNull TNTPrimed tnt) { if (!(c instanceof PlayerCraft)) return; - MovecraftLocation midpoint = c.getHitBox().getMidPoint(); - int distX = Math.abs(midpoint.getX() - tnt.getLocation().getBlockX()); - int distY = Math.abs(midpoint.getY() - tnt.getLocation().getBlockY()); - int distZ = Math.abs(midpoint.getZ() - tnt.getLocation().getBlockZ()); - if (!hasDirector((PlayerCraft) c) || distX >= CannonDirectorDistance - || distY >= CannonDirectorDistance || distZ >= CannonDirectorDistance) + // Automatically calibrate the TNT location based on its velocity to make it closer to the firing point. + Location correctedLocation = tnt.getLocation().clone().add(tnt.getVelocity().clone().multiply(-1.2)); + Vector correctedPosition = correctedLocation.toVector(); + + HashSet craftDirectors = getCraftDirectors((PlayerCraft) c); + Player player = null; + for (DirectorData data : craftDirectors) { + if (data.getSelectedNodes().isEmpty()) { + player = data.getPlayer(); + } + } + if (player == null) { + player = getClosestDirectorFromProjectile( + craftDirectors, + correctedPosition, + CannonDirectorNodeDistance + ); + } + + if (player == null || player.getInventory().getItemInMainHand().getType() != Directors.DirectorTool) return; - Player p = getDirector((PlayerCraft) c); - if (p == null || p.getInventory().getItemInMainHand().getType() != Directors.DirectorTool) + MovecraftLocation midpoint = c.getHitBox().getMidPoint(); + int distX = Math.abs(midpoint.getX() - correctedPosition.getBlockX()); + int distY = Math.abs(midpoint.getY() - correctedPosition.getBlockY()); + int distZ = Math.abs(midpoint.getZ() - correctedPosition.getBlockZ()); + if (distX*distX + distY*distY + distZ*distZ >= CannonDirectorDistance*CannonDirectorDistance) return; // Store the speed to add it back in later, since all the values we will be using are "normalized", IE: have a speed of 1 @@ -117,10 +139,10 @@ private void processTNT(@NotNull TNTPrimed tnt) { double horizontalSpeed = tntVector.length(); tntVector = tntVector.normalize(); // you normalize it for comparison with the new direction to see if we are trying to steer too far - Block targetBlock = DirectorUtils.getDirectorBlock(p, CannonDirectorRange); + Block targetBlock = DirectorUtils.getDirectorBlock(player, CannonDirectorRange); Vector targetVector; if (targetBlock == null || targetBlock.getType().equals(Material.AIR)) // the player is looking at nothing, shoot in that general direction - targetVector = p.getLocation().getDirection(); + targetVector = player.getLocation().getDirection(); else // shoot directly at the block the player is looking at (IE: with convergence) targetVector = targetBlock.getLocation().toVector().subtract(tnt.getLocation().toVector()); @@ -207,8 +229,15 @@ public final void onSignClick(@NotNull PlayerInteractEvent e) { return; } + Set selectedLines = processSign(sign); + if (isNodesShared(selectedLines, foundCraft, p)) { + p.sendMessage(ERROR_PREFIX + " " + I18nSupport.getInternationalisedString("CannonDirector - Must Not Share Nodes")); + return; + } + clearDirector(p); - addDirector(foundCraft, p); + addDirector(p, foundCraft, selectedLines); + p.sendMessage(I18nSupport.getInternationalisedString("CannonDirector - Directing")); } diff --git a/src/main/java/net/countercraft/movecraft/combat/features/directors/DirectorData.java b/src/main/java/net/countercraft/movecraft/combat/features/directors/DirectorData.java new file mode 100644 index 00000000..e0bb8fcd --- /dev/null +++ b/src/main/java/net/countercraft/movecraft/combat/features/directors/DirectorData.java @@ -0,0 +1,71 @@ +package net.countercraft.movecraft.combat.features.directors; + +import net.countercraft.movecraft.MovecraftLocation; +import net.countercraft.movecraft.craft.PlayerCraft; +import org.bukkit.block.Block; +import org.bukkit.block.Sign; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; + +import java.util.HashSet; +import java.util.Set; + +public class DirectorData { + private final Player player; + private final PlayerCraft craft; + private final Set selectedNodes; + private Set signLocations = new HashSet<>(); + private MovecraftLocation currentLocation; + + public DirectorData(Player player, PlayerCraft craft, Set selectedNodes) { + this.player = player; + this.craft = craft; + this.selectedNodes = selectedNodes; + } + + @NotNull + public Set getSignLocations() { + if (selectedNodes.isEmpty()) return signLocations; + //If the craft stays in the same position, return the already known sign locations. + MovecraftLocation midpoint = craft.getHitBox().getMidPoint(); + if (currentLocation == null) currentLocation = midpoint; + if (currentLocation.equals(midpoint) && !signLocations.isEmpty()) { + return signLocations; + } + + currentLocation = midpoint; + for (MovecraftLocation location : craft.getHitBox()) { + Block block = craft.getWorld().getBlockAt( + location.getX(), + location.getY(), + location.getZ() + ); + if (!(block.getState() instanceof Sign)) continue; + Sign sign = (Sign) block.getState(); + + if (!sign.getLine(0).equalsIgnoreCase("subcraft rotate")) continue; + if (sign.getLine(3).isBlank()) continue; + if (!selectedNodes.contains(sign.getLine(3))) continue; + + Vector relativeVector = sign.getLocation().toVector(); + signLocations.add(relativeVector); + } + return signLocations; + } + + @NotNull + public Player getPlayer() { + return player; + } + + @NotNull + public PlayerCraft getCraft() { + return craft; + } + + @NotNull + public Set getSelectedNodes() { + return selectedNodes; + } +} \ No newline at end of file diff --git a/src/main/java/net/countercraft/movecraft/combat/features/directors/Directors.java b/src/main/java/net/countercraft/movecraft/combat/features/directors/Directors.java index 5ff507f6..fd6b7042 100644 --- a/src/main/java/net/countercraft/movecraft/combat/features/directors/Directors.java +++ b/src/main/java/net/countercraft/movecraft/combat/features/directors/Directors.java @@ -1,25 +1,27 @@ package net.countercraft.movecraft.combat.features.directors; -import com.google.common.collect.HashBiMap; import net.countercraft.movecraft.combat.MovecraftCombat; import net.countercraft.movecraft.craft.PlayerCraft; import net.countercraft.movecraft.util.Tags; import org.bukkit.Material; +import org.bukkit.block.Sign; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.util.Vector; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Set; public class Directors extends BukkitRunnable { private static final Set instances = new HashSet<>(); public static Material DirectorTool = null; public static Set Transparent = null; - private final HashBiMap directors = HashBiMap.create(); - + private Map directors = new HashMap<>(); public Directors() { instances.add(this); @@ -60,41 +62,83 @@ public void run() { } + public void addDirector(Player player, PlayerCraft craft, Set selectedLines) { + if (directors.containsValue(player)) directors.remove(player); + DirectorData data = new DirectorData(player, craft, selectedLines); + directors.put(player, data); + } + + @NotNull + public HashSet getCraftDirectors(@NotNull PlayerCraft craft) { + HashSet directorDataSet = new HashSet<>(); + for (DirectorData directorData : directors.values()) { + if (directorData.getCraft() == craft) directorDataSet.add(directorData); + } + return directorDataSet; + } - public void addDirector(@NotNull PlayerCraft craft, @NotNull Player player) { - if (directors.containsValue(player)) - directors.inverse().remove(player); - directors.put(craft, player); + public boolean hasDirector(@Nullable PlayerCraft craft) { + if (craft == null) return false; + for (DirectorData data : directors.values()) { + if (data.getCraft() == craft) return true; + } + return false; } - public boolean isDirector(@NotNull Player player) { - return directors.containsValue(player); + //This ensures that no two director nodes are shared between the director players. + public boolean isNodesShared(Set selectedStrings, PlayerCraft craft, Player player) { + for (DirectorData directorData : getCraftDirectors(craft)) { + if (directorData.getPlayer() == player) continue; + if (selectedStrings.isEmpty()) return false; + Set stringsCopy = new HashSet<>(selectedStrings); + stringsCopy.retainAll(directorData.getSelectedNodes()); + if (!stringsCopy.isEmpty()) return true; + } + return false; + } + + @NotNull + public Set processSign(Sign sign) { + String[] lines = sign.getLines(); + Set selectedLines = new HashSet<>(); + + for (int i = 1; i < lines.length ; i++) { + String line = lines[i].trim(); + if (!line.isBlank()) selectedLines.add(line); + } + + return selectedLines; } - public boolean hasDirector(@NotNull PlayerCraft craft) { - if (!directors.containsKey(craft)) - return false; + @Nullable + public Player getClosestDirectorFromProjectile( + Set directorDataSet, + Vector projectile, + int nodeDistance + ) { + for (DirectorData directorData : directorDataSet) { + for (Vector signLocation : directorData.getSignLocations()) { + // Calculate squared distance. + if (signLocation.distanceSquared(projectile) <= (nodeDistance * nodeDistance)) { + return directorData.getPlayer(); + } + } + } + return null; + } - Player director = directors.get(craft); - return director != null && director.isOnline(); + public boolean isDirector(@NotNull Player player) { + return directors.containsKey(player); } public void removeDirector(@NotNull Player player) { - directors.inverse().remove(player); + directors.remove(player); } + //This clears all DirectorData that might be already assigned to the player. public void clearDirector(@NotNull Player player) { for (var instance : instances) instance.removeDirector(player); } - - @Nullable - public Player getDirector(@NotNull PlayerCraft craft) { - Player director = directors.get(craft); - if (director == null || !director.isOnline()) - return null; - - return director; - } } diff --git a/src/main/java/net/countercraft/movecraft/combat/features/tracking/FireballTracking.java b/src/main/java/net/countercraft/movecraft/combat/features/tracking/FireballTracking.java index 4ff27e89..fe83bc06 100644 --- a/src/main/java/net/countercraft/movecraft/combat/features/tracking/FireballTracking.java +++ b/src/main/java/net/countercraft/movecraft/combat/features/tracking/FireballTracking.java @@ -68,11 +68,7 @@ public void onProjectileLaunch(@NotNull ProjectileLaunchEvent e) { PlayerCraft playerCraft = (PlayerCraft) craft; - Player sender; - if (directors.hasDirector(playerCraft)) - sender = directors.getDirector(playerCraft); - else - sender = playerCraft.getPilot(); + Player sender = playerCraft.getPilot(); if (sender == null) return; diff --git a/src/main/java/net/countercraft/movecraft/combat/features/tracking/TNTTracking.java b/src/main/java/net/countercraft/movecraft/combat/features/tracking/TNTTracking.java index 89cbb86c..892ced51 100644 --- a/src/main/java/net/countercraft/movecraft/combat/features/tracking/TNTTracking.java +++ b/src/main/java/net/countercraft/movecraft/combat/features/tracking/TNTTracking.java @@ -108,11 +108,7 @@ public void onEntitySpawn (@NotNull EntitySpawnEvent e) { // Report to tracking PlayerCraft playerCraft = (PlayerCraft) craft; - Player sender; - if (directors.hasDirector(playerCraft)) - sender = directors.getDirector(playerCraft); - else - sender = playerCraft.getPilot(); + Player sender = playerCraft.getPilot(); if (sender == null) return; diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 0a48ca7c..b1137f69 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -5,10 +5,12 @@ Locale: "en" # Language file to load, currently the plugin comes with en and no, # AA Directors AADirectorDistance: 50 # Max range at which fireballs will be redirected +AADirectorNodeDistance: 3 # See node directors AADirectorRange: 120 # Max range at which it will direct to a block vs in the general direction # Cannon Directors CannonDirectorDistance: 100 # Max range at which TNT will be redirected +CannonDirectorNodeDistance: 3 # See node directors CannonDirectorRange: 120 # Max range at which it will direct to a block vs in the general direction # Directors diff --git a/src/main/resources/localisation/mcclang_en.properties b/src/main/resources/localisation/mcclang_en.properties index 3cfbad1a..29d037eb 100644 --- a/src/main/resources/localisation/mcclang_en.properties +++ b/src/main/resources/localisation/mcclang_en.properties @@ -1,11 +1,13 @@ AADirector\ -\ Directing=You are now directing the AA of this craft AADirector\ -\ No\ Longer\ Directing=You are no longer directing the AA of this craft AADirector\ -\ Not\ Allowed\ On\ Craft=AA Director signs are not allowed on this type of craft +AADirector\ -\ Must\ Not\ Share\ Nodes=You must not share nodes with other AA directors Combat\ Release=Combat Release\! Combat\ Release\ Message=Combat releasing is not allowed\! CannonDirector\ -\ Directing=You are now directing the cannons of this craft CannonDirector\ -\ No\ Longer\ Directing=You are no longer directing the cannons of this craft CannonDirector\ -\ Not\ Allowed\ On\ Craft=Cannon Director signs are not allowed on this type of craft +CannonDirector\ -\ Must\ Not\ Share\ Nodes=You must not share nodes with other cannon directors Command\ -\ Must\ Be\ Player=You must be a player to use that command. Command\ -\ Current\ Mode=Current mode Command\ -\ Current\ Setting=Current setting