Fixed:
- Plugin incorrectly reporting update available if installed version was newer than github version.

Added:
- Arm pose position menu.  Each Arm can now be individually posed using the menu.
- Radius View Button. This button initiates a red sprite outline of the scan and slaughter radius that stays for 30 seconds.
- Show/Hide Name button.  This button hides/shows the RS MobStand name above the MobStand.
This commit is contained in:
BigPappa committed 2026-09-02 15:21:03 -05:00
1 parent e702c96cad
commit ffd84d2bfa
9 files changed
+1281 -49

No files matched your search

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

+1 -1
View File
@@ -13,7 +13,7 @@
<groupId>com.rsscripting</groupId>
<artifactId>rs-mobstand</artifactId>
<version>1.1.3</version>
<version>1.2.0</version>
<name>RS-MobStand</name>
@@ -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;
}
}
@@ -182,9 +182,10 @@ public class RSAdminMenu {
);
}
else if (!currentVersion.equalsIgnoreCase(
else if (VersionChecker.compareVersions(
currentVersion,
latestVersion
)) {
) < 0) {
lore.add(
ChatColor.GRAY
@@ -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);
@@ -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,47 +981,41 @@ 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) {
armorStand.setCustomNameVisible(
visible
);
player.sendMessage(
ChatColor.RED
+ "You must remove all items from the MobStand's hands before disabling its arms."
StandMenu.open(
plugin,
player,
armorStand
);
return;
}
boolean arms =
armorStand.hasArms();
// Arm Poses
if (event.getSlot() == 22) {
arms = !arms;
if (!armorStand.hasArms()) {
armorStand.setArms(arms);
armorStand.setArms(true);
player.sendMessage(
arms
? ChatColor.GREEN
+ "MobStand arms added."
: ChatColor.GREEN
+ "MobStand arms removed."
ChatColor.GREEN
+ "MobStand arms enabled."
);
}
StandMenu.open(
ArmPoseMenu.open(
plugin,
player,
armorStand
@@ -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) {
@@ -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;
}
}
@@ -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
);
}
}
}
@@ -90,15 +90,22 @@ public class VersionChecker {
/*
|--------------------------------------------------------------------------
| UPDATE AVAILABLE
| VERSION COMPARISON
|--------------------------------------------------------------------------
*/
if (!latestVersion.equalsIgnoreCase(
"Unknown"
) && !currentVersion.equalsIgnoreCase(
if (!latestVersion.equalsIgnoreCase("Unknown")) {
int comparison =
compareVersions(
currentVersion,
latestVersion
)) {
);
/*
* Current version is older than GitHub version.
*/
if (comparison < 0) {
plugin.getLogger().warning(
"================================================="
@@ -124,6 +131,19 @@ public class VersionChecker {
}
/*
* Current version is equal to or newer than GitHub version.
*/
else {
plugin.getLogger().info(
"RS-MobStand is up to date."
);
}
}
/*
|--------------------------------------------------------------------------
| 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;