RotatingHeadsPlus
A professional Minecraft plugin that animates player heads (blocks), armor stands, and living entities with smooth, fully configurable rotations, body-part poses, particle effects, floating holograms, and rich click actions — all driven by YAML files with no required dependencies.
- Supports Minecraft 1.16 – 1.21.11 / 26.1.x (Spigot, Paper, Purpur, Folia)
- Requires Java 21 or newer
- ProtocolLib (optional) — smooth packet-based rotation and per-player stalking
- PlaceholderAPI (optional) — exposes plugin statistics as placeholders
- DecentHolograms / FancyHolograms (optional) — high-performance floating text; falls back to native ArmorStand holograms
- Body-part animation — per-part or keyframe poses for ArmorStands (no extra plugins)
- Dynamic equipment — animate ArmorStand gear by frame sequence
- Particle effects — circle, spiral, wave, aura (no extra plugins)
- Multilingual: en_US, es_ES (including hologram lines)
- Java 21, pure Spigot API — no NMS, no reflection (except skull textures via AuthLib)
Table of Contents
- Installation
- Configuration — config.yml
- Animating objects — data/*.yml
- Animation types
- Composite animations
- Body-part animation (ArmorStands)
- Conditions
- Click actions
- Holograms
- Particles
- Equipment (ArmorStands)
- Frame animations — animations/*.yml
- Commands
- Troubleshooting
- Permissions
- Optional dependencies
- Placeholders
- Developer API
- Folder structure
- Complete example — ArmorStand showcase
Installation
- Drop RotatingHeadsPlus.jar into plugins/.
- Start the server once — all default files are generated automatically.
- Edit plugins/RotatingHeadsPlus/config.yml as needed.
- Create objects with /rhead create or write data/*.yml files by hand, then /rhead reload.
Configuration — config.yml
Code (YAML):
# Language file to use. Files live in plugins/RotatingHeadsPlus/lang/.
# Bundled: en_US, es_ES
language
: en_US
# Default values used when /rhead create is run without explicit parameters.
defaultSpeed
: 5.0
# Yaw degrees added per tick
defaultInterval
: 2
# Ticks between animation updates
# Max distance (blocks) /rhead remove searches for the nearest animated object.
maxRemovalDistance
: 3.0
# Set true to print additional diagnostic output to the console.
debug
: false
# Packet-based rotation — requires ProtocolLib.
rotation
:
# Set false to force teleport-based rotation even if ProtocolLib is present.
usePackets
: true
# Players farther than this many blocks receive no rotation packets.
# Lower values improve performance on large maps with many animated objects.
packetRange
: 128
# Ticks between a server-side sync teleport when using packets.
# Prevents stale rotation for players who join or reload their chunk.
# Minimum: 1. Default: 40 (every 2 s at 20 tps).
syncInterval
: 40
Folia support is automatic — the plugin detects Folia at runtime and selects the correct scheduler internally. No configuration is needed. The folia.force-mode option in config.yml exists only for debugging and advanced use cases (AUTO | FOLIA | BUKKIT).
Animating objects — data/*.yml
Files in plugins/RotatingHeadsPlus/data/ define persistent animated objects. Each file represents
one object. File names are the object's UUID (e.g. 550e8400-e29b-41d4-a716-446655440000.yml).
The plugin writes these files automatically when you use /rhead create. You can also write them by hand and reload with /rhead reload.
Head block
A PLAYER_HEAD block at fixed world coordinates.
Code (YAML):
location:
world
: world
x
: 100
y
: 64
z
: 200
animation:
type
: circular
startYaw
: 0.0
speed
: 5.0
interval
: 2
Armor stand
An existing ArmorStand entity identified by its UUID. Use a plugin such as EntityInfo, or check the console output of /rhead create after right-clicking the stand.
Code (YAML):
entity:
world
: world
uuid
:
"550e8400-e29b-41d4-a716-446655440000"
type
: armorstand
animation:
type
: pingpong
minYaw
: -45.0
maxYaw
: 45.0
step
: 3.0
interval
: 2
Living entity
Any living entity (Villager, Zombie, …) referenced by UUID.
Code (YAML):
entity:
world
: world
uuid
:
"661f9511-f3ac-52e5-b827-557766551111"
type
: entity
animation:
type
: stalking
maxDistance
: 10.0
interval
: 1
Animation types
All types share the interval field:
how many server ticks between updates. Use 1 for maximum smoothness (every tick). Default is 2.
circular
Rotates continuously in one direction.
Code (YAML):
animation:
type
: circular
startYaw
: 0.0
# Starting angle in degrees
speed
: 5.0
# Degrees added per tick (positive = clockwise when viewed from above)
interval
: 2
pingpong
Oscillates back and forth between two yaw angles.
Code (YAML):
animation:
type
: pingpong
minYaw
: -45.0
# Left boundary (degrees)
maxYaw
: 45.0
# Right boundary (degrees)
step
: 3.0
# Degrees moved per tick
interval
: 2
static
Locks the object at a fixed angle. Applied once; interval is ignored.
Code (YAML):
animation:
type
: static
yaw
: 90.0
# Fixed angle in degrees
stalking
Rotates to face the nearest online player each tick.
Mode
Behaviour
With ProtocolLib Each player individually sees the entity looking at them (per-player packets). No server-side teleport is made.
Without ProtocolLib Finds the single nearest player and applies one server-side rotation globally.
PLAYER_HEAD blocks require ProtocolLib for smooth stalking — without it the block updates are server-side only and may appear jerky.
Code (YAML):
animation:
type
: stalking
maxDistance
: 10.0
# Ignore players farther than this (blocks). 0 = unlimited.
interval
: 1
updown
Moves the entity vertically in a sinusoidal (floating) pattern. Works for
ArmorStands and living entities only — PLAYER_HEAD blocks cannot move vertically and are silently skipped.
Code (YAML):
animation:
type
: updown
amplitude
: 0.5
# Max vertical displacement in blocks (±amplitude around base Y)
speed
: 0.05
# Phase increment per tick in radians.
# 0.05 ≈ one full cycle every ~6 s at 20 tps
# 0.10 ≈ one full cycle every ~3 s at 20 tps
interval
: 1
The base Y is captured from the entity's position on the
first tick so the floating always centres on the entity's spawn position, surviving reloads cleanly.
Composite animations
Run multiple animations on the same object simultaneously by using the plural animations: key instead of animation:.
Animations are applied in
list order. When two animations write the same property (e.g. both set yaw), the one listed
last wins.
Recommended order: place rotation animations (circular, pingpong, stalking) before movement animations (updown) so yaw is set before the vertical teleport fires.
Code (YAML):
animations:
- type
: circular
speed
: 5.0
interval
: 2
- type
: updown
amplitude
: 0.4
speed
: 0.05
interval
: 1
Mixing rotation with body-part / keyframe animations
You can combine a whole-body rotation with bodyParts: or keyframes: by writing both at the top level of the same file. The plugin automatically merges them into a composite — no extra syntax needed:
Code (YAML):
entity:
world
: world
uuid
:
"550e8400-e29b-41d4-a716-446655440000"
type
: armorstand
# Whole-body rotation
animation:
type
: circular
speed
: 3.0
interval
: 2
# Body-part keyframe animation runs simultaneously
keyframes:
- frame
: 0
rightArm
:
[-10, 0, 0
]
leftArm
:
[ 10, 0, 0
]
rightLeg
:
[-10, 0, 0
]
leftLeg
:
[ 10, 0, 0
]
- frame
: 15
rightArm
:
[-50, 0, 0
]
leftArm
:
[ 50, 0, 0
]
rightLeg
:
[-25, 0, 0
]
leftLeg
:
[ 25, 0, 0
]
- frame
: 30
rightArm
:
[-10, 0, 0
]
leftArm
:
[ 10, 0, 0
]
rightLeg
:
[-10, 0, 0
]
leftLeg
:
[ 10, 0, 0
]
loop
: true
interpolation
: smooth
The same shorthand works with bodyParts: instead of keyframes:, and also with the animations: list format — see
Combining with whole-body rotation for the explicit list syntax.
Body-part animation (ArmorStands)
Animate the individual body parts of an ArmorStand using Euler angles. Works alongside whole-body yaw rotation. Has no effect on PLAYER_HEAD blocks or generic entities.
Two exclusive modes are available:
per-part and
keyframe.
Per-part mode
Each body part gets its own independent animation. Use the top-level bodyParts: key.
Supported parts: head, body, leftArm, rightArm, leftLeg, rightLeg
Supported animation types per part: static, pingpong, circular
static — fixed pose
Code (YAML):
bodyParts:
leftArm:
animation
: static
angle
:
[0, 0, -10
]
# [x, y, z] in degrees
pingpong — oscillates between two poses
Code (YAML):
bodyParts:
head:
animation
: pingpong
minAngle
:
[-15, 0, 0
]
# Lower bound [x, y, z] in degrees
maxAngle
:
[ 15, 0, 0
]
# Upper bound [x, y, z] in degrees
speed
: 2.0
# Degrees per tick (applied per axis independently)
The animation moves from minAngle toward maxAngle, then reverses. Axes where min = max are held fixed.
circular — continuously increments one axis
Code (YAML):
bodyParts:
rightArm:
animation
: circular
speed
: 3.0
# Degrees per tick on the chosen axis
axis
: X
# Which Euler axis to rotate: X, Y, or Z
The other two axes remain at 0. Pair with a static entry for the same part if you need a non-zero base.
Full per-part example
Code (YAML):
entity:
world
: world
uuid
:
"550e8400-e29b-41d4-a716-446655440000"
type
: armorstand
bodyParts:
head:
animation
: pingpong
minAngle
:
[-20, 0, 0
]
maxAngle
:
[ 20, 0, 0
]
speed
: 1.5
rightArm:
animation
: circular
speed
: 4.0
axis
: X
leftArm:
animation
: static
angle
:
[0, 0, -15
]
rightLeg:
animation
: pingpong
minAngle
:
[-15, 0, 0
]
maxAngle
:
[ 15, 0, 0
]
speed
: 2.0
leftLeg:
animation
: pingpong
minAngle
:
[ 15, 0, 0
]
maxAngle
:
[-15, 0, 0
]
speed
: 2.0
Keyframe mode
Define a sequence of full-body poses at specific tick indices. The plugin interpolates between them automatically. Use the top-level keyframes: key.
Code (YAML):
entity:
world
: world
uuid
:
"550e8400-e29b-41d4-a716-446655440000"
type
: armorstand
keyframes:
- frame
: 0
head
:
[ 0, 0, 0
]
rightArm
:
[-10, 0, 0
]
leftArm
:
[ 10, 0, 0
]
rightLeg
:
[-10, 0, 0
]
leftLeg
:
[ 10, 0, 0
]
- frame
: 15
head
:
[ 15, 0, 0
]
rightArm
:
[-50, 0, -10
]
leftArm
:
[ 50, 0, 10
]
rightLeg
:
[-25, 0, 0
]
leftLeg
:
[ 25, 0, 0
]
- frame
: 30
head
:
[ 0, 0, 0
]
rightArm
:
[-10, 0, 0
]
leftArm
:
[ 10, 0, 0
]
rightLeg
:
[-10, 0, 0
]
leftLeg
:
[ 10, 0, 0
]
loop
: true
interpolation
: smooth
# linear | smooth | step
Notes:
- Angles are [x, y, z] in degrees; converted to radians internally.
- Not every part needs to appear in every keyframe — missing parts are held from the nearest defined keyframe.
- frame values are tick indices (0-based). Gaps between frames are interpolated over that many ticks.
- The last keyframe should mirror the first for seamless looping.
Interpolation modes
Mode
Description
linear Straight linear blend. Fast and predictable.
smooth Cosine ease-in / ease-out: (1 - cos(t·π)) / 2. Natural, organic feel.
Recommended for character poses.
step No blending — holds the earlier keyframe's pose until the next frame is reached. Good for mechanical/snapping animations.
Built-in presets
Two presets are available programmatically (for developers using the API or as reference YAML configurations):
Preset
Description
wave() Right arm swings from down to up (0 °→ −90 ° on X) and back. Uses PINGPONG at 3 °/tick.
walk() Opposing arm/leg walking cycle over 30 ticks. Three keyframes, SMOOTH interpolation, looping.
Combining with whole-body rotation
bodyParts: / keyframes: only animate the body part angles. To
also spin the ArmorStand in yaw, embed a bodypart entry inside a composite animations: list:
Code (YAML):
entity:
world
: world
uuid
:
"550e8400-e29b-41d4-a716-446655440000"
type
: armorstand
animations:
- type
: circular
speed
: 3.0
interval
: 2
- type
: bodypart
bodyParts:
head:
animation
: pingpong
minAngle
:
[-20, 0, 0
]
maxAngle
:
[ 20, 0, 0
]
speed
: 2.0
rightArm:
animation
: circular
speed
: 4.0
axis
: X
Or with keyframes inside a composite:
Code (YAML):
animations:
- type
: circular
speed
: 2.0
interval
: 2
- type
: bodypart
keyframes:
- frame
: 0
rightArm
:
[-10, 0, 0
]
leftArm
:
[ 10, 0, 0
]
- frame
: 20
rightArm
:
[-60, 0, 0
]
leftArm
:
[ 60, 0, 0
]
loop
: true
interpolation
: smooth
Conditions
Conditions gate when an animation runs. They are optional and apply to both data/*.yml and animations/*.yml.
All condition fields are optional — omit any field you do not need.
Code (YAML):
conditions:
world
: world
# Only animate in this world
minDistance
: 1.0
# Minimum distance from a player (blocks)
maxDistance
: 10.0
# Maximum distance from a player (blocks)
startTime
:
"08:00"
# Start time (24 h HH:mm, real-world clock)
endTime
:
"20:00"
# End time
Distance behaviour: the animation runs only when at least one online player is inside [minDistance, maxDistance] of the object.
Time ranges crossing midnight are supported: startTime: "22:00" + endTime: "06:00" activates between 10 PM and 6 AM.
Click actions
Click actions fire when a player clicks an animated object.
Triggers
Key
Fires when
LEFT Player left-clicks the object
RIGHT Player right-clicks the object
BOTH Any click (fires in addition to the specific trigger, not instead of it)
Action string format
TYPE:value[|cooldown=N][|permission=perm.node][|world=worldName]
Modifiers are
optional, order-independent, and backward-compatible (old entries without modifiers continue to work).
Action types
Type
Description
Value format
message Chat message to the clicking player &aHello %player%!
title Title + optional subtitle separated by :: &6Title::&7Subtitle
command Command run
as the player warp spawn
console_cmd Command run by the
server console give %player% diamond 1
broadcast Message sent to all online players &e%player% activated it!
sound Play a sound at the player's location ENTITY_EXPERIENCE_ORB_PICKUP
actionbar Action-bar message &eYou clicked!
teleport Teleport player to absolute coordinates world,100,64,200 or world,x,y,z,yaw,pitch
potion Apply a potion effect SPEED,200,1 (type, ticks, amplifier)
connect Send player to a BungeeCord server lobby
Modifiers
Modifier
Description
|cooldown=N Per-player cooldown in
seconds. The action is silently skipped while active.
|permission=node Player
must have this permission node to trigger the action. Prefix ! to
negate (player must
not have it).
|world=name Player must be in this world for the action to fire.
Placeholder support
All action values support %player% (always available) and full PlaceholderAPI placeholders when PAPI is installed.
Full example
Code (YAML):
click-actions:
LEFT
:
-
"sound:ENTITY_EXPERIENCE_ORB_PICKUP"
-
"message:&aYou left-clicked, %player%!"
-
"title:&6Shop::&7Click RIGHT to buy|cooldown=10"
RIGHT
:
-
"console_cmd:give %player% diamond 1|permission=shop.use|cooldown=60"
-
"message:&aYou received a diamond!"
-
"message:&cNo permission.|permission=!shop.use"
BOTH
:
-
"broadcast:&c%player% activated the statue!"
-
"actionbar:&eThanks for clicking!|world=world"
-
"connect:lobby|permission=hub.vip"
-
"teleport:world,0,64,0|cooldown=300"
-
"potion:SPEED,100,1|cooldown=30"
Holograms
Add floating text above any animated object. The plugin automatically selects the best available provider.
Code (YAML):
hologram:
lines
:
-
"&6{lang:holo.title}"
-
"&7{lang:holo.subtitle}"
-
"%rotatingheadsplus_count% active"
offsetY
: 1.5
# Blocks above the object's base position (default 1.5)
updateInterval
: 20
# Ticks between text refresh — relevant when lines contain PAPI placeholders
followAnimation
: false
# Set true when using updown — hologram moves with the entity
Line content
Syntax
Description
{lang:key} Resolved from the active language file. Keys: holo.title, holo.subtitle, holo.active, holo.inactive
%placeholder% PAPI placeholder — resolved per-viewer by DH/FH; shown as literal text by Native.
&x colour codes Standard Minecraft colour codes translated automatically.
Provider priority
Priority
Provider
Requires
1
DecentHolograms DecentHolograms plugin enabled
2
FancyHolograms FancyHolograms plugin enabled
3
Native (ArmorStand) Nothing — always available
The active provider is logged at startup: [HologramManager] Using provider: DecentHolograms
followAnimation
Required when using updown so the hologram moves with the entity:
Code (YAML):
animations:
- type
: circular
speed
: 5.0
interval
: 2
- type
: updown
amplitude
: 0.4
speed
: 0.05
interval
: 1
hologram:
lines
:
-
"&b{lang:holo.title}"
offsetY
: 2.0
followAnimation
: true
Particles
Decorative particle effects around any animated object. Uses the Bukkit particle API — no extra plugins needed.
Code (YAML):
particles:
enabled
: true
type
: FLAME
# Any Bukkit Particle enum name (case-insensitive)
pattern
: circle
# circle | spiral | wave | aura
count
: 8
# Particles emitted per interval
radius
: 1.0
# Horizontal radius (blocks)
speed
: 0.0
# Extra velocity applied to physics-affected particles
color
:
"#FF5500"
# Hex RGB — only used for REDSTONE / DUST particles
offsetY
: 1.0
# Height above the object's base position (blocks)
interval
: 2
# Ticks between emissions
Patterns
Pattern
Description
circle Particles evenly spaced on a slowly-rotating horizontal ring.
spiral Helix pattern — particles rise upward and reset, creating a continuous stream.
wave Ring with a sinusoidal Y-offset that ripples over time.
aura Particles scattered randomly inside a sphere for an ambient glow.
Colour particles (REDSTONE / DUST)
Set type: REDSTONE (or DUST on 1.20.5+) and provide color: "#RRGGBB". The plugin handles the version API rename automatically — both names work on all supported server versions.
Code (YAML):
particles:
enabled
: true
type
: REDSTONE
pattern
: aura
count
: 12
radius
: 0.8
color
:
"#00AAFF"
offsetY
: 1.0
interval
: 3
Tracking UpDown position
When the object uses updown, particles automatically follow the entity's live Y position every tick — no extra configuration required.
Equipment (ArmorStands)
Set and animate the gear worn or held by an ArmorStand. Two sub-modes:
static (set once at load time) and
animated (cycle through frames over time).
Equipment slots: helmet, chestplate, leggings, boots, mainhand, offhand
Static equipment
Set items once when the data file is loaded. Items persist until the plugin reloads or the file is changed.
Code (YAML):
entity:
world
: world
uuid
:
"550e8400-e29b-41d4-a716-446655440000"
type
: armorstand
equipment:
helmet:
type
: PLAYER_HEAD
texture
:
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvIn19fQ=="
chestplate:
type
: DIAMOND_CHESTPLATE
enchanted
: true
leggings:
type
: IRON_LEGGINGS
boots:
type
: IRON_BOOTS
mainhand:
type
: DIAMOND_SWORD
customModelData
: 1001
offhand:
type
: SHIELD
Animated equipment (frames)
Cycle through equipment changes at specific tick indices. Between frames, the current item is held unchanged.
Code (YAML):
entity:
world
: world
uuid
:
"550e8400-e29b-41d4-a716-446655440000"
type
: armorstand
equipmentFrames:
- frame
: 0
mainhand:
type
: WOODEN_SWORD
- frame
: 40
mainhand:
type
: STONE_SWORD
- frame
: 80
mainhand:
type
: IRON_SWORD
- frame
: 120
mainhand:
type
: DIAMOND_SWORD
- frame
: 160
mainhand:
type
: NETHERITE_SWORD
equipmentLoop
: true
# Restart after the last frame? (default true)
All six slots can appear in any frame. Slots omitted from a frame are
not changed when that frame fires.
Item definition reference
Each item slot accepts the following fields:
Field
Type
Description
type String
Required. Bukkit Material name (case-insensitive). E.g. DIAMOND_SWORD, PLAYER_HEAD.
customModelData Integer Sets the custom model data for resource pack model overrides. 0 = not set.
enchanted Boolean Applies a visual enchantment glow without showing any enchantments. Default false.
displayName String Custom display name. Supports & colour codes.
owner String
PLAYER_HEAD only. Sets the skull to the skin of this player name.
texture String
PLAYER_HEAD only. Base64-encoded texture value (from a site like minecraft-heads.com). Takes priority over owner.
Obtaining a texture value: on sites like
minecraft-heads.com, look for the "Value" field in the give-command section — that long Base64 string is the texture value.
Code (YAML):
# Skull with texture (decorative head)
helmet:
type
: PLAYER_HEAD
texture
:
"eyJ0ZXh0dXJlcyI6..."
# Skull by player name (shows the player's current skin)
helmet:
type
: PLAYER_HEAD
owner
:
"Notch"
# Custom-model sword with glow
mainhand:
type
: IRON_SWORD
customModelData
: 2001
enchanted
: true
displayName
:
"&6Legendary Sword"
Frame animations — animations/*.yml
Files in plugins/RotatingHeadsPlus/animations/ are managed by AnimationExecutor. These are independent of data/*.yml — they are not saved back on server stop.
Each frame is [yaw, pitch] in degrees. Pitch rotation is supported here (useful for entities with a head tilt).
Code (YAML):
type
: armorstand
# head | armorstand | entity
location:
world
: world
x
: 100.5
y
: 65.0
z
: 200.5
loop
: true
# Repeat after the last frame?
interval
: 10
# Ticks between frames (20 ticks = 1 second)
frames
:
-
[0, 0
]
-
[90, 0
]
-
[180, 0
]
-
[270, 0
]
# Conditions and click-actions work here too
conditions:
minDistance
: 1.0
maxDistance
: 8.0
startTime
:
"06:00"
endTime
:
"18:00"
click-actions:
RIGHT
:
-
"message:&bFrame animation clicked!"
Key differences from data/*.yml:
Feature
data/*.yml
animations/*.yml
Persistence Saved on server stop Loaded fresh from file each time
Yaw + Pitch Yaw only Both supported
Body parts Supported (bodyParts / keyframes keys) Not supported
Equipment Supported (equipment / equipmentFrames keys) Not supported
Hologram Supported Not supported
Particles Supported Not supported
Commands
All commands use /rhead (aliases: /rheads, /rh). Tab completion is available for all subcommands and UUID arguments.
Command
Description
Permission
/rhead create Registers the block or entity you are looking at rotatingheadsplus.use
/rhead remove Removes the nearest animated object within maxRemovalDistance rotatingheadsplus.use
/rhead stop Pauses animation on the nearest object (keeps it registered) rotatingheadsplus.admin
/rhead list Shows the count of currently animated objects rotatingheadsplus.use
/rhead info [uuid] Shows full details about the nearest (or specified) animated object rotatingheadsplus.use
/rhead near [radius] Lists all animated objects within a radius (default 20 blocks) rotatingheadsplus.use
/rhead tp <uuid> Teleports you to an animated object by full or partial UUID rotatingheadsplus.admin
/rhead copy Copies the nearest object's full config to your clipboard rotatingheadsplus.admin
/rhead paste Pastes the clipboard config onto the entity/block you are looking at rotatingheadsplus.admin
/rhead export [filename] Exports the nearest object to exports/<filename>.yml rotatingheadsplus.admin
/rhead import <filename> Imports an object from exports/<filename>.yml and registers it rotatingheadsplus.admin
/rhead relink <uuid> Reassigns a data file to a replacement entity (see
Recovering a broken ArmorStand) rotatingheadsplus.admin
/rhead debug Prints diagnostic information about the plugin state rotatingheadsplus.admin
/rhead reload Reloads config, language files, and all data/animation YAMLs rotatingheadsplus.admin
/rhead help Displays the command reference in-game rotatingheadsplus.use
Troubleshooting
Recovering a broken ArmorStand
If an animated ArmorStand is accidentally destroyed (broken by a player, killed by a command, etc.) and replaced with a new one, the new entity has a
different UUID. The data file on disk still references the old UUID, so /rhead reload will not restore the animation — the console will log:
[DataManager] Entity not found for UUID <old-uuid> (type: armorstand).
The entity may have been destroyed or is in an unloaded chunk.
Use /rhead relink <uuid> while looking at the replacement entity to reassign it.
Recovery steps:
- Place a new ArmorStand at the same position.
- Run /rhead near to find the old UUID listed in the data folder, or check the console warning.
- Look directly at the new ArmorStand.
- Run:
/rhead relink <old-uuid>
Tab-completion lists all UUIDs from the data/ folder, including orphaned ones.
- The plugin will:
- Update entity.uuid in the data file to the new entity's UUID.
- Rename the file from <old-uuid>.yml to <new-uuid>.yml.
- Re-register the animation, holograms, particles, and equipment immediately — no reload needed.
Note: /rhead relink only works for entity-based objects (ArmorStands and living entities). Head blocks use block coordinates, not UUIDs, so they are unaffected by this issue.
Entity in an unloaded chunk
If the console shows the "Entity not found" warning but the ArmorStand was not destroyed, the chunk may simply be unloaded. Load the chunk (walk near it or use a chunk-loading plugin) and run /rhead reload. The animation will resume automatically.
Heads auto-rotating on placement
If every placed PLAYER_HEAD starts rotating automatically, check config.yml:
autoRotateOnPlace: false # set to false (default) — only /rhead create animates heads
Set it to false and run /rhead reload.
Permissions
Node
Default
Description
rotatingheadsplus.use true Create, remove, list, and help commands
rotatingheadsplus.admin op Stop and reload commands
Optional dependencies
ProtocolLib
Purpose: packet-based rotation as an alternative to teleport.
Benefits when installed:
- Smoother visual rotation — no teleport jitter, no gravity resets on ArmorStands, no PlayerTeleportEvent spam
- stalking animation works per-player: each player sees the entity looking directly at them
- Packets sent only to players within rotation.packetRange blocks (default 128) — better performance at scale
- Server-side sync teleport every rotation.syncInterval ticks ensures correct rotation for players who join mid-animation
Without ProtocolLib: the plugin works fully using teleport-based rotation.
Control via config.yml: set rotation.usePackets: false to disable packet rotation even when ProtocolLib is present.
DecentHolograms
Provides per-player PAPI placeholder resolution and high-performance hologram rendering. If absent, falls back to FancyHolograms or Native automatically.
FancyHolograms
Alternative hologram provider with per-player PAPI support (used when DecentHolograms is absent). If absent, falls back to Native (ArmorStand) holograms.
PlaceholderAPI
Required to expose the plugin's own placeholders (listed below) to other plugins. Also enables per-player PAPI resolution inside action values (e.g. console_cmd:eco give %player_name% 100).
Without PAPI: the plugin works fully; %player% substitution in actions still works.
Placeholders
Requires PlaceholderAPI.
Placeholder
Returns
%rotatingheadsplus_count% Number of currently animated objects
%rotatingheadsplus_enabled% true if the plugin is active
Developer API
RotatingHeadsPlus provides a clean, fully-functional Java API for other plugins. All methods delegate directly to live subsystems — no stubs, no simulated data.
Add the plugin as a dependency in your plugin.yml:
Code (Java):
depend
:
[RotatingHeadsPlus
]
# or softdepend
if optional
:
softdepend
:
[RotatingHeadsPlus
]
Getting the instance
Code (Java):
RotatingHeadsAPI api
= RotatingHeadsAPI.
getInstance
(
)
;
if
(api
==
null
)
{
// Plugin not loaded — handle gracefully
return
;
}
Registration
Code (Java):
// Register a PLAYER_HEAD block
Location loc
= ...
;
Rotatable head
= api.
registerHead
(loc, RotatingHeadsAPI.
circular
(5f,
2
)
)
;
// Register an existing ArmorStand
ArmorStand stand
= ...
;
// must already be spawned
Rotatable r
= api.
registerArmorStand
(stand, RotatingHeadsAPI.
pingPong
(
-45f, 45f, 3f,
2
)
)
;
// Register any living entity
Entity entity
= ...
;
Rotatable re
= api.
registerEntity
(entity, RotatingHeadsAPI.
stalking
(
10.0,
1
)
)
;
// Fully remove (unregisters from all subsystems)
api.
removeRotatable
(r.
getUniqueId
(
)
)
;
Animation control
Code (Java):
UUID id
= r.
getUniqueId
(
)
;
// Swap animation (fires AnimationChangeEvent)
api.
setAnimation
(id, RotatingHeadsAPI.
upDown
(
0.4,
0.05,
1
)
)
;
// Stop animation without unregistering (fires AnimationRemoveEvent)
api.
stopAnimation
(id
)
;
// Assign a condition
AnimationCondition cond
=
new AnimationCondition
(
"world",
1.0,
15.0,
"08:00",
"22:00"
)
;
api.
setCondition
(id, cond
)
;
// Query
boolean animated
= api.
isAnimated
(id
)
;
HeadAnimation anim
= api.
getAnimation
(id
)
;
Set
<UUID
> all
= api.
getAllAnimatedIds
(
)
;
int count
= api.
getAnimatedCount
(
)
;
Custom events
All events are in package com.me.koyere.rotatingheadsplus.api.event.
Event
Fired
Cancellable
Code (Java):
AnimationStartEvent Before a
new object is registered Yes — cancelling returns
null from register
*
AnimationChangeEvent Before an animation swap Yes — also allows replacing newAnimation
AnimationRemoveEvent Before removal or stop Yes — isFullRemoval
(
) distinguishes the two cases
RotatableClickEvent When a player clicks an animated object Yes — cancelling suppresses all click
-action chains
@EventHandler
public
void onAnimStart
(AnimationStartEvent event
)
{
if
(
!allowedWorlds.
contains
(event.
getRotatable
(
).
getLocation
(
).
getWorld
(
).
getName
(
)
)
)
{
event.
setCancelled
(
true
)
;
}
}
@EventHandler
public
void onAnimChange
(AnimationChangeEvent event
)
{
// Force all incoming animations to use interval 1
// (not possible via setNewAnimation alone, but demonstrates the pattern)
getLogger
(
).
info
(
"Animation changed on "
+ event.
getObjectId
(
)
)
;
}
@EventHandler
public
void onRotatableClick
(RotatableClickEvent event
)
{
if
(event.
getTrigger
(
)
== ClickTrigger.
RIGHT
&& blockedIds.
contains
(event.
getObjectId
(
)
)
)
{
event.
setCancelled
(
true
)
;
}
}
Animation factories
Static helper methods on RotatingHeadsAPI create all built-in animation types:
Method
Description
RotatingHeadsAPI.circular(speed, interval) Continuous yaw rotation
RotatingHeadsAPI.pingPong(min, max, step, interval) Oscillating yaw
RotatingHeadsAPI.staticRotation(yaw) Fixed angle
RotatingHeadsAPI.stalking(maxDistance, interval) Faces nearest player
RotatingHeadsAPI.upDown(amplitude, speed, interval) Vertical float
RotatingHeadsAPI.composite(anim1, anim2, ...) Multiple simultaneous
RotatingHeadsAPI.bodyParts(Map<BodyPart, PartConfig>) Per-part EulerAngle
Equipment and subsystems
Code (Java):
// Hologram
api.
setHologram
(id,
new HologramConfig
(
List.
of
(
"&6My Object",
"&7Animated"
),
1.8,
20,
false
)
)
;
api.
removeHologram
(id
)
;
boolean hasHolo
= api.
hasHologram
(id
)
;
// Particles
api.
setParticles
(id,
new ParticleConfig
(Particle.
FLAME, ParticlePattern.
SPIRAL, ...
)
)
;
api.
removeParticles
(id
)
;
// Static equipment (ArmorStand only)
Map
<ArmorSlot, ItemDefinition
> items
=
new EnumMap
<>
(ArmorSlot.
class
)
;
items.
put
(ArmorSlot.
HELMET,
new ItemDefinition
(Material.
DIAMOND_HELMET,
0,
false,
null,
null,
null
)
)
;
api.
setStaticEquipment
(id, items
)
;
// Animated equipment
List
<EquipmentFrame
> frames
=
List.
of
(
new EquipmentFrame
(
0,
Map.
of
(ArmorSlot.
MAIN_HAND,
new ItemDefinition
(Material.
WOODEN_SWORD, ...
)
)
),
new EquipmentFrame
(
40,
Map.
of
(ArmorSlot.
MAIN_HAND,
new ItemDefinition
(Material.
DIAMOND_SWORD, ...
)
)
)
)
;
api.
setAnimatedEquipment
(id, frames,
true
)
;
// true = loop
api.
removeEquipment
(id
)
;
// Click actions
api.
setClickActions
(id, ClickTrigger.
RIGHT,
List.
of
(ActionEntry.
parse
(
"message:&aHello!"
)
)
)
;
List
<ActionEntry
> actions
= api.
getClickActions
(id, ClickTrigger.
RIGHT
)
;
Folder structure
Code (YAML):
plugins/
└── RotatingHeadsPlus/
├── config.yml
# Main configuration
├── lang/
│ ├── en_US.yml
# English messages
│ └── es_ES.yml
# Spanish messages
├── data/
│ └── <uuid>.yml
# One file per persistent animated object
├── animations/
│ └── my_animation.yml
# Frame-based animations (pitch + yaw, not saved on stop)
├── exports/
│ └── my_export.yml
# Files written by /rhead export; read by /rhead import
└── examples/
├── head_circular.yml
├── head_pingpong.yml
├── head_timed_conditional.yml
├── armorstand_rotation.yml
├── entity_spin.yml
└── click_actions_example.yml
Example files are copied to examples/ on first startup for reference only. Copy them to data/ or animations/ to use them.
Complete example — ArmorStand showcase
A single data/*.yml file combining every system:
Code (YAML):
entity:
world
: world
uuid
:
"550e8400-e29b-41d4-a716-446655440000"
type
: armorstand
# Whole-body: rotate slowly and float
animations:
- type
: circular
speed
: 2.0
interval
: 2
- type
: updown
amplitude
: 0.3
speed
: 0.04
interval
: 1
# Body parts: head looks around, arms wave
bodyParts:
head:
animation
: pingpong
minAngle
:
[-20, 0, 0
]
maxAngle
:
[ 20, 0, 0
]
speed
: 1.5
rightArm:
animation
: pingpong
minAngle
:
[-10, 0, 0
]
maxAngle
:
[-80, 0, 0
]
speed
: 2.5
leftArm:
animation
: pingpong
minAngle
:
[ 10, 0, 0
]
maxAngle
:
[ 80, 0, 0
]
speed
: 2.5
# Equipment: sword changes over time
equipmentFrames:
- frame
: 0
helmet:
type
: PLAYER_HEAD
texture
:
"eyJ0ZXh0dXJlcyI6..."
mainhand:
type
: WOODEN_SWORD
- frame
: 60
mainhand:
type
: DIAMOND_SWORD
enchanted
: true
customModelData
: 1001
equipmentLoop
: true
# Conditions: only animate when a player is nearby during daytime
conditions:
maxDistance
: 20.0
startTime
:
"06:00"
endTime
:
"22:00"
# Hologram: floats with the entity
hologram:
lines
:
-
"&6{lang:holo.title}"
-
"&7{lang:holo.subtitle}"
offsetY
: 2.2
updateInterval
: 20
followAnimation
: true
# Particles: enchantment sparkles rising up
particles:
enabled
: true
type
: ENCHANTMENT_TABLE
pattern
: spiral
count
: 6
radius
: 0.7
offsetY
: 0.5
interval
: 2
# Click actions: right-click gives speed
click-actions:
RIGHT
:
-
"sound:ENTITY_EXPERIENCE_ORB_PICKUP"
-
"potion:SPEED,200,1|cooldown=30"
-
"message:&aSpeed boost applied for 10 seconds!|cooldown=30"
-
"message:&cCooldown active.|permission=!rotatingheadsplus.use|cooldown=30"
LEFT
:
-
"title:&6Showcase::&7Powered by RotatingHeadsPlus"
-
"actionbar:&eRight-click for a speed boost!"
License
Proprietary software. All Rights Reserved © 2024–present Koyere Dev. Unauthorized distribution, modification, or resale is strictly prohibited.
Credits
Developed by
Koyere Dev. Inspired by the original RotatingHeads 2 by Gennario — rewritten from scratch with extended functionality.