diff --git a/docs/images/ArmPoses.jpg b/docs/images/ArmPoses.jpg
new file mode 100644
index 0000000..5fdbe41
Binary files /dev/null and b/docs/images/ArmPoses.jpg differ
diff --git a/pom.xml b/pom.xml
index 3b7a6cb..f7491b3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -13,7 +13,7 @@
com.rsscripting
rs-mobstand
- 1.1.3
+ 1.2.0
RS-MobStand
diff --git a/src/main/java/com/rsscripting/mobstand/gui/ArmPoseMenu.java b/src/main/java/com/rsscripting/mobstand/gui/ArmPoseMenu.java
new file mode 100644
index 0000000..30238dd
--- /dev/null
+++ b/src/main/java/com/rsscripting/mobstand/gui/ArmPoseMenu.java
@@ -0,0 +1,372 @@
+package com.rsscripting.mobstand.gui;
+
+import com.rsscripting.mobstand.RSMobStandPlugin;
+import com.rsscripting.mobstand.utils.ArmPoseUtils;
+import com.rsscripting.mobstand.utils.RSMenuUtils;
+import org.bukkit.Bukkit;
+import org.bukkit.ChatColor;
+import org.bukkit.Material;
+import org.bukkit.enchantments.Enchantment;
+import org.bukkit.entity.ArmorStand;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.ItemFlag;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+
+import java.util.List;
+
+public final class ArmPoseMenu {
+
+ public static final String MENU_ID =
+ "ARMPOSE";
+
+ private ArmPoseMenu() {
+ // Utility-style menu class
+ }
+
+ public static void open(
+ RSMobStandPlugin plugin,
+ Player player,
+ ArmorStand armorStand
+ ) {
+
+ Inventory inventory =
+ Bukkit.createInventory(
+ new RSMenuHolder(MENU_ID),
+ 36,
+ ChatColor.BLUE + "Arm Poses"
+ );
+
+ ItemStack filler =
+ RSMenuUtils.createFillerPane();
+
+ for (int i = 0; i < inventory.getSize(); i++) {
+
+ inventory.setItem(
+ i,
+ filler
+ );
+ }
+
+ /*
+ |--------------------------------------------------------------------------
+ | BACK
+ |--------------------------------------------------------------------------
+ */
+
+ ItemStack backItem =
+ new ItemStack(
+ Material.ARROW
+ );
+
+ ItemMeta backMeta =
+ backItem.getItemMeta();
+
+ if (backMeta != null) {
+
+ backMeta.setDisplayName(
+ ChatColor.YELLOW
+ + "Back"
+ );
+
+ backMeta.setLore(
+ List.of(
+ ChatColor.GRAY
+ + "Return to MobStand settings."
+ )
+ );
+
+ backItem.setItemMeta(
+ backMeta
+ );
+ }
+
+ inventory.setItem(
+ 4,
+ backItem
+ );
+
+ /*
+ |--------------------------------------------------------------------------
+ | ARMS DISABLED
+ |--------------------------------------------------------------------------
+ */
+
+ if (!armorStand.hasArms()) {
+
+ ItemStack disabled =
+ new ItemStack(
+ Material.BARRIER
+ );
+
+ ItemMeta disabledMeta =
+ disabled.getItemMeta();
+
+ if (disabledMeta != null) {
+
+ disabledMeta.setDisplayName(
+ ChatColor.RED
+ + "Arms Disabled"
+ );
+
+ disabledMeta.setLore(
+ List.of(
+ ChatColor.GRAY
+ + "Enable Arms from the",
+ ChatColor.GRAY
+ + "main MobStand menu first."
+ )
+ );
+
+ disabled.setItemMeta(
+ disabledMeta
+ );
+ }
+
+ inventory.setItem(
+ 13,
+ disabled
+ );
+
+ inventory.setItem(
+ 22,
+ disabled.clone()
+ );
+
+ player.openInventory(
+ inventory
+ );
+
+ return;
+ }
+
+ /*
+ |--------------------------------------------------------------------------
+ | MAIN HAND
+ |--------------------------------------------------------------------------
+ */
+
+ inventory.setItem(
+ 9,
+ createLabel(
+ Material.IRON_SWORD,
+ "Main Hand"
+ )
+ );
+
+ ArmPoseUtils.Pose mainHandPose =
+ ArmPoseUtils.getMainHandPose(
+ armorStand
+ );
+
+ inventory.setItem(
+ 10,
+ createPoseItem(
+ "Down",
+ ArmPoseUtils.Pose.DOWN,
+ mainHandPose
+ )
+ );
+
+ inventory.setItem(
+ 11,
+ createPoseItem(
+ "1/4 Down",
+ ArmPoseUtils.Pose.QUARTER_DOWN,
+ mainHandPose
+ )
+ );
+
+ inventory.setItem(
+ 12,
+ createPoseItem(
+ "Straight Out",
+ ArmPoseUtils.Pose.STRAIGHT_OUT,
+ mainHandPose
+ )
+ );
+
+ inventory.setItem(
+ 13,
+ createPoseItem(
+ "1/4 Up",
+ ArmPoseUtils.Pose.QUARTER_UP,
+ mainHandPose
+ )
+ );
+
+ inventory.setItem(
+ 14,
+ createPoseItem(
+ "Straight Up",
+ ArmPoseUtils.Pose.STRAIGHT_UP,
+ mainHandPose
+ )
+ );
+
+ /*
+ |--------------------------------------------------------------------------
+ | OFF HAND
+ |--------------------------------------------------------------------------
+ */
+
+ inventory.setItem(
+ 18,
+ createLabel(
+ Material.SHIELD,
+ "Off Hand"
+ )
+ );
+
+ ArmPoseUtils.Pose offHandPose =
+ ArmPoseUtils.getOffHandPose(
+ armorStand
+ );
+
+ inventory.setItem(
+ 19,
+ createPoseItem(
+ "Down",
+ ArmPoseUtils.Pose.DOWN,
+ offHandPose
+ )
+ );
+
+ inventory.setItem(
+ 20,
+ createPoseItem(
+ "1/4 Down",
+ ArmPoseUtils.Pose.QUARTER_DOWN,
+ offHandPose
+ )
+ );
+
+ inventory.setItem(
+ 21,
+ createPoseItem(
+ "Straight Out",
+ ArmPoseUtils.Pose.STRAIGHT_OUT,
+ offHandPose
+ )
+ );
+
+ inventory.setItem(
+ 22,
+ createPoseItem(
+ "1/4 Up",
+ ArmPoseUtils.Pose.QUARTER_UP,
+ offHandPose
+ )
+ );
+
+ inventory.setItem(
+ 23,
+ createPoseItem(
+ "Straight Up",
+ ArmPoseUtils.Pose.STRAIGHT_UP,
+ offHandPose
+ )
+ );
+
+ player.openInventory(
+ inventory
+ );
+ }
+
+ private static ItemStack createLabel(
+ Material material,
+ String name
+ ) {
+
+ ItemStack item =
+ new ItemStack(
+ material
+ );
+
+ ItemMeta meta =
+ item.getItemMeta();
+
+ if (meta != null) {
+
+ meta.setDisplayName(
+ ChatColor.AQUA
+ + name
+ );
+
+ meta.setLore(
+ List.of(
+ ChatColor.GRAY
+ + "Select an arm pose below."
+ )
+ );
+
+ meta.addItemFlags(
+ ItemFlag.HIDE_ATTRIBUTES
+ );
+
+ item.setItemMeta(
+ meta
+ );
+ }
+
+ return item;
+ }
+
+ private static ItemStack createPoseItem(
+ String name,
+ ArmPoseUtils.Pose pose,
+ ArmPoseUtils.Pose current
+ ) {
+
+ ItemStack item =
+ new ItemStack(
+ Material.ARMOR_STAND
+ );
+
+ ItemMeta meta =
+ item.getItemMeta();
+
+ if (meta != null) {
+
+ boolean selected =
+ pose == current;
+
+ meta.setDisplayName(
+ selected
+ ? ChatColor.GREEN + name
+ : ChatColor.YELLOW + name
+ );
+
+ meta.setLore(
+ List.of(
+ selected
+ ? ChatColor.GREEN
+ + "Currently selected."
+ : ChatColor.GRAY
+ + "Click to select."
+ )
+ );
+
+ if (selected) {
+
+ meta.addEnchant(
+ Enchantment.UNBREAKING,
+ 1,
+ true
+ );
+
+ meta.addItemFlags(
+ ItemFlag.HIDE_ENCHANTS,
+ ItemFlag.HIDE_ATTRIBUTES
+ );
+ }
+
+ item.setItemMeta(
+ meta
+ );
+ }
+
+ return item;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/rsscripting/mobstand/gui/RSAdminMenu.java b/src/main/java/com/rsscripting/mobstand/gui/RSAdminMenu.java
index d029a50..787c940 100644
--- a/src/main/java/com/rsscripting/mobstand/gui/RSAdminMenu.java
+++ b/src/main/java/com/rsscripting/mobstand/gui/RSAdminMenu.java
@@ -182,9 +182,10 @@ public class RSAdminMenu {
);
}
- else if (!currentVersion.equalsIgnoreCase(
+ else if (VersionChecker.compareVersions(
+ currentVersion,
latestVersion
- )) {
+ ) < 0) {
lore.add(
ChatColor.GRAY
diff --git a/src/main/java/com/rsscripting/mobstand/gui/StandMenu.java b/src/main/java/com/rsscripting/mobstand/gui/StandMenu.java
index 5781712..86796de 100644
--- a/src/main/java/com/rsscripting/mobstand/gui/StandMenu.java
+++ b/src/main/java/com/rsscripting/mobstand/gui/StandMenu.java
@@ -34,7 +34,7 @@ public class StandMenu {
new RSMenuHolder(
"MAIN"
),
- 27,
+ 36,
MENU_TITLE
);
@@ -185,6 +185,44 @@ public class StandMenu {
inventory.setItem(6, pauseItem);
+// View Radius item
+ ItemStack viewRadiusItem =
+ new ItemStack(
+ Material.ENDER_EYE
+ );
+
+ ItemMeta viewRadiusMeta =
+ viewRadiusItem.getItemMeta();
+
+ if (viewRadiusMeta != null) {
+
+ viewRadiusMeta.setDisplayName(
+ ChatColor.GREEN
+ + "View Radius"
+ );
+
+ viewRadiusMeta.setLore(List.of(
+ ChatColor.GRAY
+ + "Display the current scan area.",
+ ChatColor.GRAY
+ + "Shows horizontal radius",
+ ChatColor.GRAY
+ + "and vertical scan height.",
+ "",
+ ChatColor.YELLOW
+ + "Click to view."
+ ));
+
+ viewRadiusItem.setItemMeta(
+ viewRadiusMeta
+ );
+ }
+
+ inventory.setItem(
+ 31,
+ viewRadiusItem
+ );
+
@@ -238,6 +276,47 @@ public class StandMenu {
inventory.setItem(17, babyFilters);
+ // Display Name item
+ ItemStack displayNameItem =
+ new ItemStack(Material.NAME_TAG);
+
+ ItemMeta displayNameMeta =
+ displayNameItem.getItemMeta();
+
+ if (displayNameMeta != null) {
+
+ boolean visible =
+ armorStand.isCustomNameVisible();
+
+ displayNameMeta.setDisplayName(
+ (visible
+ ? ChatColor.GREEN
+ : ChatColor.RED)
+ + "Display Name"
+ );
+
+ displayNameMeta.setLore(List.of(
+ ChatColor.GRAY + "Show or hide the",
+ ChatColor.GRAY + "RS MobStand name.",
+ "",
+ ChatColor.GRAY + "Current: "
+ + (visible
+ ? ChatColor.GREEN + "Visible"
+ : ChatColor.RED + "Hidden"),
+ "",
+ ChatColor.YELLOW + "Click to toggle."
+ ));
+
+ displayNameItem.setItemMeta(
+ displayNameMeta
+ );
+ }
+
+ inventory.setItem(
+ 20,
+ displayNameItem
+ );
+
// Leave Alive item
int leaveAlive =
@@ -355,7 +434,7 @@ public class StandMenu {
inventory.setItem(11, gravityItem);
-// Arms toggle
+// Arm Poses
ItemStack armsItem =
new ItemStack(
Material.IRON_SWORD
@@ -367,21 +446,28 @@ public class StandMenu {
if (armsMeta != null) {
armsMeta.setDisplayName(
- (arms
- ? ChatColor.GREEN
- : ChatColor.RED)
- + "Arms"
+ arms
+ ? ChatColor.GREEN + "Arm Poses"
+ : ChatColor.RED + "Arm Poses"
);
armsMeta.setLore(List.of(
- ChatColor.GRAY + "Current: "
+ ChatColor.GRAY + "Configure the Main Hand",
+ ChatColor.GRAY + "and Off Hand arm poses.",
+ "",
+ ChatColor.GRAY + "Arms: "
+ (arms
? ChatColor.GREEN + "Enabled"
: ChatColor.RED + "Disabled"),
"",
- ChatColor.YELLOW + "Click to toggle."
+ ChatColor.YELLOW
+ + "Click to configure."
));
+ armsMeta.addItemFlags(
+ ItemFlag.HIDE_ATTRIBUTES
+ );
+
if (arms) {
armsMeta.addEnchant(
@@ -391,17 +477,19 @@ public class StandMenu {
);
armsMeta.addItemFlags(
- ItemFlag.HIDE_ENCHANTS,
- ItemFlag.HIDE_ATTRIBUTES
+ ItemFlag.HIDE_ENCHANTS
);
-
}
- armsItem.setItemMeta(armsMeta);
-
+ armsItem.setItemMeta(
+ armsMeta
+ );
}
- inventory.setItem(22, armsItem);
+ inventory.setItem(
+ 22,
+ armsItem
+ );
player.openInventory(inventory);
diff --git a/src/main/java/com/rsscripting/mobstand/listeners/MenuListener.java b/src/main/java/com/rsscripting/mobstand/listeners/MenuListener.java
index 0911145..ae12e14 100644
--- a/src/main/java/com/rsscripting/mobstand/listeners/MenuListener.java
+++ b/src/main/java/com/rsscripting/mobstand/listeners/MenuListener.java
@@ -5,7 +5,9 @@ import com.rsscripting.mobstand.data.MobFilterData;
import com.rsscripting.mobstand.gui.*;
import com.rsscripting.mobstand.managers.StandManager;
import com.rsscripting.mobstand.gui.RSAdminMenu;
+import com.rsscripting.mobstand.utils.ArmPoseUtils;
+import com.rsscripting.mobstand.utils.RadiusVisualizer;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.entity.ArmorStand;
@@ -691,7 +693,157 @@ public class MenuListener implements Listener {
}
-// Stand Menu
+/* =========================================================
+ ARM POSE MENU
+ ========================================================= */
+
+ if (event.getInventory()
+ .getHolder()
+ instanceof RSMenuHolder holder
+ && holder.getMenuId()
+ .equals(
+ "ARMPOSE"
+ )) {
+
+ event.setCancelled(true);
+
+ UUID standUUID =
+ standManager.getSelectedArmorStand(
+ player.getUniqueId()
+ );
+
+ if (standUUID == null) {
+ player.closeInventory();
+ return;
+ }
+
+ ArmorStand armorStand =
+ (ArmorStand) Bukkit.getEntity(
+ standUUID
+ );
+
+ if (armorStand == null) {
+ player.closeInventory();
+ return;
+ }
+
+ int slot = event.getRawSlot();
+
+ // Back button
+ if (slot == 4) {
+
+ StandMenu.open(
+ plugin,
+ player,
+ armorStand
+ );
+
+ return;
+ }
+
+ // Arms must be enabled
+ if (!armorStand.hasArms()) {
+
+ player.sendMessage(
+ ChatColor.RED
+ + "Enable arms before configuring arm poses."
+ );
+
+ return;
+ }
+
+ // Main Hand
+ if (slot == 10) {
+
+ ArmPoseUtils.setMainHandPose(
+ armorStand,
+ ArmPoseUtils.Pose.DOWN
+ );
+
+ } else if (slot == 11) {
+
+ ArmPoseUtils.setMainHandPose(
+ armorStand,
+ ArmPoseUtils.Pose.QUARTER_DOWN
+ );
+
+ } else if (slot == 12) {
+
+ ArmPoseUtils.setMainHandPose(
+ armorStand,
+ ArmPoseUtils.Pose.STRAIGHT_OUT
+ );
+
+ } else if (slot == 13) {
+
+ ArmPoseUtils.setMainHandPose(
+ armorStand,
+ ArmPoseUtils.Pose.QUARTER_UP
+ );
+
+ } else if (slot == 14) {
+
+ ArmPoseUtils.setMainHandPose(
+ armorStand,
+ ArmPoseUtils.Pose.STRAIGHT_UP
+ );
+
+ // Off Hand
+ } else if (slot == 19) {
+
+ ArmPoseUtils.setOffHandPose(
+ armorStand,
+ ArmPoseUtils.Pose.DOWN
+ );
+
+ } else if (slot == 20) {
+
+ ArmPoseUtils.setOffHandPose(
+ armorStand,
+ ArmPoseUtils.Pose.QUARTER_DOWN
+ );
+
+ } else if (slot == 21) {
+
+ ArmPoseUtils.setOffHandPose(
+ armorStand,
+ ArmPoseUtils.Pose.STRAIGHT_OUT
+ );
+
+ } else if (slot == 22) {
+
+ ArmPoseUtils.setOffHandPose(
+ armorStand,
+ ArmPoseUtils.Pose.QUARTER_UP
+ );
+
+ } else if (slot == 23) {
+
+ ArmPoseUtils.setOffHandPose(
+ armorStand,
+ ArmPoseUtils.Pose.STRAIGHT_UP
+ );
+
+ } else {
+
+ return;
+ }
+
+ // Refresh the Arm Poses menu
+ ArmPoseMenu.open(
+ plugin,
+ player,
+ armorStand
+ );
+
+ return;
+ }
+
+
+/* =========================================================
+ STAND MENU
+ ========================================================= */
+
if (event.getInventory()
.getHolder()
instanceof RSMenuHolder holder
@@ -829,44 +981,16 @@ public class MenuListener implements Listener {
}
-// Arms toggle
- if (event.getSlot() == 22) {
+ // Display Name toggle
+ if (event.getSlot() == 20) {
- boolean mainHandOccupied =
- !armorStand.getEquipment()
- .getItemInMainHand()
- .getType()
- .isAir();
+ boolean visible =
+ armorStand.isCustomNameVisible();
- boolean offHandOccupied =
- !armorStand.getEquipment()
- .getItemInOffHand()
- .getType()
- .isAir();
+ visible = !visible;
- if (mainHandOccupied || offHandOccupied) {
-
- player.sendMessage(
- ChatColor.RED
- + "You must remove all items from the MobStand's hands before disabling its arms."
- );
-
- return;
- }
-
- boolean arms =
- armorStand.hasArms();
-
- arms = !arms;
-
- armorStand.setArms(arms);
-
- player.sendMessage(
- arms
- ? ChatColor.GREEN
- + "MobStand arms added."
- : ChatColor.GREEN
- + "MobStand arms removed."
+ armorStand.setCustomNameVisible(
+ visible
);
StandMenu.open(
@@ -878,6 +1002,28 @@ public class MenuListener implements Listener {
return;
}
+// Arm Poses
+ if (event.getSlot() == 22) {
+
+ if (!armorStand.hasArms()) {
+
+ armorStand.setArms(true);
+
+ player.sendMessage(
+ ChatColor.GREEN
+ + "MobStand arms enabled."
+ );
+ }
+
+ ArmPoseMenu.open(
+ plugin,
+ player,
+ armorStand
+ );
+
+ return;
+ }
+
// Open leave Alive Menu
if (event.getSlot() == 13) {
@@ -906,6 +1052,26 @@ public class MenuListener implements Listener {
return;
}
+ // View Radius
+ if (event.getSlot() == 31) {
+
+ double radius =
+ standManager.getRadius(
+ armorStand
+ );
+
+ player.closeInventory();
+
+ RadiusVisualizer.show(
+ plugin,
+ player,
+ armorStand,
+ radius
+ );
+
+ return;
+ }
+
// Gravity toggle
if (event.getSlot() == 11) {
diff --git a/src/main/java/com/rsscripting/mobstand/utils/ArmPoseUtils.java b/src/main/java/com/rsscripting/mobstand/utils/ArmPoseUtils.java
new file mode 100644
index 0000000..016d83a
--- /dev/null
+++ b/src/main/java/com/rsscripting/mobstand/utils/ArmPoseUtils.java
@@ -0,0 +1,174 @@
+package com.rsscripting.mobstand.utils;
+
+import org.bukkit.entity.ArmorStand;
+import org.bukkit.util.EulerAngle;
+
+public final class ArmPoseUtils {
+
+ private ArmPoseUtils() {
+ // Utility class
+ }
+
+ public enum Pose {
+ DOWN,
+ QUARTER_DOWN,
+ STRAIGHT_OUT,
+ QUARTER_UP,
+ STRAIGHT_UP
+ }
+
+ public static void setMainHandPose(
+ ArmorStand armorStand,
+ Pose pose
+ ) {
+
+ armorStand.setRightArmPose(
+ getRightArmPose(pose)
+ );
+ }
+
+ public static void setOffHandPose(
+ ArmorStand armorStand,
+ Pose pose
+ ) {
+
+ armorStand.setLeftArmPose(
+ getLeftArmPose(pose)
+ );
+ }
+
+ public static Pose getMainHandPose(
+ ArmorStand armorStand
+ ) {
+
+ return findClosestPose(
+ armorStand.getRightArmPose()
+ );
+ }
+
+ public static Pose getOffHandPose(
+ ArmorStand armorStand
+ ) {
+
+ return findClosestPose(
+ armorStand.getLeftArmPose()
+ );
+ }
+
+ private static EulerAngle getRightArmPose(
+ Pose pose
+ ) {
+
+ return switch (pose) {
+
+ case DOWN ->
+ new EulerAngle(
+ 0,
+ 0,
+ 0
+ );
+
+ case QUARTER_DOWN ->
+ new EulerAngle(
+ Math.toRadians(-45),
+ 0,
+ 0
+ );
+
+ case STRAIGHT_OUT ->
+ new EulerAngle(
+ Math.toRadians(-90),
+ 0,
+ 0
+ );
+
+ case QUARTER_UP ->
+ new EulerAngle(
+ Math.toRadians(-135),
+ 0,
+ 0
+ );
+
+ case STRAIGHT_UP ->
+ new EulerAngle(
+ Math.toRadians(-180),
+ 0,
+ 0
+ );
+ };
+ }
+
+ private static EulerAngle getLeftArmPose(
+ Pose pose
+ ) {
+
+ return switch (pose) {
+
+ case DOWN ->
+ new EulerAngle(
+ 0,
+ 0,
+ 0
+ );
+
+ case QUARTER_DOWN ->
+ new EulerAngle(
+ Math.toRadians(-45),
+ 0,
+ 0
+ );
+
+ case STRAIGHT_OUT ->
+ new EulerAngle(
+ Math.toRadians(-90),
+ 0,
+ 0
+ );
+
+ case QUARTER_UP ->
+ new EulerAngle(
+ Math.toRadians(-135),
+ 0,
+ 0
+ );
+
+ case STRAIGHT_UP ->
+ new EulerAngle(
+ Math.toRadians(-180),
+ 0,
+ 0
+ );
+ };
+ }
+
+ private static Pose findClosestPose(
+ EulerAngle actual
+ ) {
+
+ Pose closest =
+ Pose.DOWN;
+
+ double closestDistance =
+ Double.MAX_VALUE;
+
+ for (Pose pose : Pose.values()) {
+
+ EulerAngle target =
+ getRightArmPose(pose);
+
+ double distance =
+ Math.abs(
+ actual.getX()
+ - target.getX()
+ );
+
+ if (distance < closestDistance) {
+
+ closestDistance = distance;
+ closest = pose;
+ }
+ }
+
+ return closest;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/rsscripting/mobstand/utils/RadiusVisualizer.java b/src/main/java/com/rsscripting/mobstand/utils/RadiusVisualizer.java
new file mode 100644
index 0000000..138aeff
--- /dev/null
+++ b/src/main/java/com/rsscripting/mobstand/utils/RadiusVisualizer.java
@@ -0,0 +1,362 @@
+package com.rsscripting.mobstand.utils;
+
+import com.rsscripting.mobstand.RSMobStandPlugin;
+import org.bukkit.Color;
+import org.bukkit.Location;
+import org.bukkit.Particle;
+import org.bukkit.entity.ArmorStand;
+import org.bukkit.entity.Player;
+import org.bukkit.scheduler.BukkitRunnable;
+
+public final class RadiusVisualizer {
+
+ private RadiusVisualizer() {
+ // Utility class
+ }
+
+ public static void show(
+ RSMobStandPlugin plugin,
+ Player player,
+ ArmorStand armorStand,
+ double radius
+ ) {
+
+ Location center =
+ armorStand.getLocation().clone();
+
+ new BukkitRunnable() {
+
+ int ticks = 0;
+
+ @Override
+ public void run() {
+
+ if (!player.isOnline()
+ || armorStand.isDead()) {
+
+ cancel();
+ return;
+ }
+
+ drawOutline(
+ player,
+ center,
+ radius
+ );
+
+ ticks += 10;
+
+ if (ticks >= 300) {
+ cancel();
+ }
+ }
+
+ }.runTaskTimer(
+ plugin,
+ 0L,
+ 10L
+ );
+ }
+
+ private static void drawOutline(
+ Player player,
+ Location center,
+ double radius
+ ) {
+
+ double minX =
+ center.getX() - radius;
+
+ double maxX =
+ center.getX() + radius;
+
+ double minZ =
+ center.getZ() - radius;
+
+ double maxZ =
+ center.getZ() + radius;
+
+ double bottomY =
+ center.getY() - 2;
+
+ double topY =
+ center.getY() + 2;
+
+ Particle.DustOptions red =
+ new Particle.DustOptions(
+ Color.RED,
+ 1.0F
+ );
+
+ // Bottom square
+ drawLine(
+ player,
+ new Location(
+ center.getWorld(),
+ minX,
+ bottomY,
+ minZ
+ ),
+ new Location(
+ center.getWorld(),
+ maxX,
+ bottomY,
+ minZ
+ ),
+ red
+ );
+
+ drawLine(
+ player,
+ new Location(
+ center.getWorld(),
+ maxX,
+ bottomY,
+ minZ
+ ),
+ new Location(
+ center.getWorld(),
+ maxX,
+ bottomY,
+ maxZ
+ ),
+ red
+ );
+
+ drawLine(
+ player,
+ new Location(
+ center.getWorld(),
+ maxX,
+ bottomY,
+ maxZ
+ ),
+ new Location(
+ center.getWorld(),
+ minX,
+ bottomY,
+ maxZ
+ ),
+ red
+ );
+
+ drawLine(
+ player,
+ new Location(
+ center.getWorld(),
+ minX,
+ bottomY,
+ maxZ
+ ),
+ new Location(
+ center.getWorld(),
+ minX,
+ bottomY,
+ minZ
+ ),
+ red
+ );
+
+ // Top square
+ drawLine(
+ player,
+ new Location(
+ center.getWorld(),
+ minX,
+ topY,
+ minZ
+ ),
+ new Location(
+ center.getWorld(),
+ maxX,
+ topY,
+ minZ
+ ),
+ red
+ );
+
+ drawLine(
+ player,
+ new Location(
+ center.getWorld(),
+ maxX,
+ topY,
+ minZ
+ ),
+ new Location(
+ center.getWorld(),
+ maxX,
+ topY,
+ maxZ
+ ),
+ red
+ );
+
+ drawLine(
+ player,
+ new Location(
+ center.getWorld(),
+ maxX,
+ topY,
+ maxZ
+ ),
+ new Location(
+ center.getWorld(),
+ minX,
+ topY,
+ maxZ
+ ),
+ red
+ );
+
+ drawLine(
+ player,
+ new Location(
+ center.getWorld(),
+ minX,
+ topY,
+ maxZ
+ ),
+ new Location(
+ center.getWorld(),
+ minX,
+ topY,
+ minZ
+ ),
+ red
+ );
+
+ // Four vertical corners
+ drawLine(
+ player,
+ new Location(
+ center.getWorld(),
+ minX,
+ bottomY,
+ minZ
+ ),
+ new Location(
+ center.getWorld(),
+ minX,
+ topY,
+ minZ
+ ),
+ red
+ );
+
+ drawLine(
+ player,
+ new Location(
+ center.getWorld(),
+ maxX,
+ bottomY,
+ minZ
+ ),
+ new Location(
+ center.getWorld(),
+ maxX,
+ topY,
+ minZ
+ ),
+ red
+ );
+
+ drawLine(
+ player,
+ new Location(
+ center.getWorld(),
+ maxX,
+ bottomY,
+ maxZ
+ ),
+ new Location(
+ center.getWorld(),
+ maxX,
+ topY,
+ maxZ
+ ),
+ red
+ );
+
+ drawLine(
+ player,
+ new Location(
+ center.getWorld(),
+ minX,
+ bottomY,
+ maxZ
+ ),
+ new Location(
+ center.getWorld(),
+ minX,
+ topY,
+ maxZ
+ ),
+ red
+ );
+ }
+
+ private static void drawLine(
+ Player player,
+ Location start,
+ Location end,
+ Particle.DustOptions dust
+ ) {
+
+ double distance =
+ start.distance(end);
+
+ double step =
+ 0.25;
+
+ int points =
+ Math.max(
+ 1,
+ (int) Math.ceil(
+ distance / step
+ )
+ );
+
+ for (int i = 0; i <= points; i++) {
+
+ double progress =
+ (double) i / points;
+
+ double x =
+ start.getX()
+ + (
+ end.getX()
+ - start.getX()
+ ) * progress;
+
+ double y =
+ start.getY()
+ + (
+ end.getY()
+ - start.getY()
+ ) * progress;
+
+ double z =
+ start.getZ()
+ + (
+ end.getZ()
+ - start.getZ()
+ ) * progress;
+
+ Location particleLocation =
+ new Location(
+ start.getWorld(),
+ x,
+ y,
+ z
+ );
+
+ player.spawnParticle(
+ Particle.DUST,
+ particleLocation,
+ 1,
+ dust
+ );
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/rsscripting/mobstand/utils/VersionChecker.java b/src/main/java/com/rsscripting/mobstand/utils/VersionChecker.java
index 1668e7e..0cb351b 100644
--- a/src/main/java/com/rsscripting/mobstand/utils/VersionChecker.java
+++ b/src/main/java/com/rsscripting/mobstand/utils/VersionChecker.java
@@ -88,39 +88,59 @@ public class VersionChecker {
plugin.getDescription()
.getVersion();
- /*
- |--------------------------------------------------------------------------
- | UPDATE AVAILABLE
- |--------------------------------------------------------------------------
- */
+/*
+|--------------------------------------------------------------------------
+| VERSION COMPARISON
+|--------------------------------------------------------------------------
+*/
- if (!latestVersion.equalsIgnoreCase(
- "Unknown"
- ) && !currentVersion.equalsIgnoreCase(
- latestVersion
- )) {
+ if (!latestVersion.equalsIgnoreCase("Unknown")) {
- plugin.getLogger().warning(
- "================================================="
- );
+ int comparison =
+ compareVersions(
+ currentVersion,
+ latestVersion
+ );
- plugin.getLogger().warning(
- "A new version of RS-MobStand is available!"
- );
+ /*
+ * Current version is older than GitHub version.
+ */
+ if (comparison < 0) {
- plugin.getLogger().warning(
- "Current Version: "
- + currentVersion
- );
+ plugin.getLogger().warning(
+ "================================================="
+ );
- plugin.getLogger().warning(
- "Latest Version: "
- + latestVersion
- );
+ plugin.getLogger().warning(
+ "A new version of RS-MobStand is available!"
+ );
- plugin.getLogger().warning(
- "================================================="
- );
+ plugin.getLogger().warning(
+ "Current Version: "
+ + currentVersion
+ );
+
+ plugin.getLogger().warning(
+ "Latest Version: "
+ + latestVersion
+ );
+
+ plugin.getLogger().warning(
+ "================================================="
+ );
+
+ }
+
+ /*
+ * Current version is equal to or newer than GitHub version.
+ */
+ else {
+
+ plugin.getLogger().info(
+ "RS-MobStand is up to date."
+ );
+
+ }
}
@@ -160,6 +180,55 @@ public class VersionChecker {
}
+ public static int compareVersions(
+ String currentVersion,
+ String latestVersion
+ ) {
+
+ String[] currentParts =
+ currentVersion
+ .replaceAll("[^0-9.]", "")
+ .split("\\.");
+
+ String[] latestParts =
+ latestVersion
+ .replaceAll("[^0-9.]", "")
+ .split("\\.");
+
+ int length =
+ Math.max(
+ currentParts.length,
+ latestParts.length
+ );
+
+ for (int i = 0; i < length; i++) {
+
+ int current =
+ i < currentParts.length
+ ? Integer.parseInt(
+ currentParts[i]
+ )
+ : 0;
+
+ int latest =
+ i < latestParts.length
+ ? Integer.parseInt(
+ latestParts[i]
+ )
+ : 0;
+
+ if (current < latest) {
+ return -1;
+ }
+
+ if (current > latest) {
+ return 1;
+ }
+ }
+
+ return 0;
+ }
+
public static String getLatestVersion() {
return latestVersion;