11package cc.modlabs.kpaper.visuals.display
22
3+ import cc.modlabs.kpaper.extensions.timer
4+ import cc.modlabs.kpaper.main.PluginInstance
35import cc.modlabs.kpaper.util.getLogger
46import com.github.retrooper.packetevents.PacketEvents
57import com.github.retrooper.packetevents.protocol.entity.data.EntityData
@@ -14,9 +16,12 @@ import com.github.retrooper.packetevents.util.Vector3f
1416import dev.fruxz.stacked.text
1517import org.bukkit.Location
1618import org.bukkit.entity.Player
19+ import org.bukkit.scheduler.BukkitTask
1720import java.util.*
1821import java.util.concurrent.ConcurrentHashMap
1922import java.util.concurrent.atomic.AtomicInteger
23+ import kotlin.math.cos
24+ import kotlin.math.sin
2025
2126/* *
2227 * A manager class for creating and managing packet-based Text Display entities in Minecraft.
@@ -222,6 +227,98 @@ class TextDisplayManager {
222227 }
223228 }
224229
230+ /* *
231+ * Updates the transformation of an existing Text Display.
232+ * This includes scale, translation, rotation, and interpolation settings.
233+ *
234+ * @param textDisplay The TextDisplay to update
235+ */
236+ fun updateTransformation (textDisplay : TextDisplay ) {
237+ // Send metadata update for all viewers
238+ val metadataPacket = createMetadataPacket(textDisplay)
239+ for (viewer in textDisplay.viewers) {
240+ PacketEvents .getAPI().playerManager.sendPacket(viewer, metadataPacket)
241+ }
242+ }
243+
244+ /* *
245+ * Starts continuous rotation around the Y-axis for a Text Display.
246+ * The display will rotate smoothly using interpolation.
247+ *
248+ * This is similar to PaperMC's transformation matrix rotation, but uses quaternions
249+ * for packet-based entities.
250+ *
251+ * @param textDisplay The TextDisplay to rotate
252+ * @param durationTicks The duration of half a revolution in ticks (default: 100 ticks = 5 seconds)
253+ * @param scale The scale multiplier for the display during rotation (default: 1.0 = no scaling)
254+ */
255+ fun startRotation (
256+ textDisplay : TextDisplay ,
257+ durationTicks : Int = 100,
258+ scale : Float = 1.0f
259+ ) {
260+ // Stop any existing rotation
261+ stopRotation(textDisplay)
262+
263+ // Apply scale if specified
264+ if (scale != 1.0f ) {
265+ textDisplay.scale = Vector3f (scale, scale, scale)
266+ }
267+
268+ // Initialize rotation angle to 180 degrees + small offset (to prevent reverse interpolation)
269+ textDisplay.currentRotationAngle = Math .toRadians(180.0 + 0.1 ).toFloat()
270+
271+ // Calculate rotation increment per tick
272+ // Rotate 180 degrees over the duration (half revolution)
273+ val radiansPerTick = Math .toRadians(180.0 / durationTicks).toFloat()
274+
275+ // Set interpolation duration for smooth rotation
276+ textDisplay.transformationInterpolationDuration = durationTicks
277+ textDisplay.interpolationDelay = 0
278+
279+ // Delay initial transformation by one tick (as per PaperMC example)
280+ var isFirstUpdate = true
281+
282+ // Start rotation task - update every tick
283+ textDisplay.rotationTask = timer(1 , " TextDisplayRotation-${textDisplay.entityId} " ) {
284+ // Check if display is still valid and has viewers
285+ if (textDisplay.viewers.isEmpty() || ! activeDisplays.containsKey(textDisplay.entityId)) {
286+ stopRotation(textDisplay)
287+ return @timer
288+ }
289+
290+ // Skip first tick (delay initial transformation by one tick)
291+ if (isFirstUpdate) {
292+ isFirstUpdate = false
293+ return @timer
294+ }
295+
296+ // Update rotation angle
297+ textDisplay.currentRotationAngle + = radiansPerTick
298+
299+ // Create quaternion for Y-axis rotation
300+ // Quaternion for rotation around Y-axis: (0, sin(angle/2), 0, cos(angle/2))
301+ val halfAngle = textDisplay.currentRotationAngle / 2f
302+ val quaternionY = sin(halfAngle)
303+ val quaternionW = cos(halfAngle)
304+
305+ // Update left rotation (this rotates the display around Y-axis)
306+ textDisplay.leftRotation = Quaternion4f (0f , quaternionY, 0f , quaternionW)
307+
308+ // Update transformation with new rotation
309+ updateTransformation(textDisplay)
310+ }
311+ }
312+
313+ /* *
314+ * Stops the continuous rotation for a Text Display.
315+ *
316+ * @param textDisplay The TextDisplay to stop rotating
317+ */
318+ fun stopRotation (textDisplay : TextDisplay ) {
319+ textDisplay.stopRotation()
320+ }
321+
225322 /* *
226323 * Updates the position of an existing Text Display.
227324 *
@@ -246,6 +343,9 @@ class TextDisplayManager {
246343 * @param textDisplay The TextDisplay to remove
247344 */
248345 fun removeTextDisplay (textDisplay : TextDisplay ) {
346+ // Stop any rotation task
347+ stopRotation(textDisplay)
348+
249349 activeDisplays.remove(textDisplay.entityId)
250350
251351 for (viewer in textDisplay.viewers.toSet()) {
@@ -484,15 +584,24 @@ class TextDisplayManager {
484584 val opacity : Int = -1 ,
485585 val displayFlags : List <TextDisplayFlags >,
486586 val backgroundColor : Int = 0x00000000 ,
487- val scale : Vector3f = Vector3f (1f, 1f, 1f),
488- val viewRange : Float = 1f ,
489- val translation : Vector3f = Vector3f (0f, 0f, 0f),
490- val leftRotation : Quaternion4f = Quaternion4f (0f, 0f, 0f, 1f),
491- val rightRotation : Quaternion4f = Quaternion4f (0f, 0f, 0f, 1f),
492- val interpolationDelay : Int = 0 ,
493- val transformationInterpolationDuration : Int = 0 ,
494- val positionRotationInterpolationDuration : Int = 0 ,
587+ var scale : Vector3f = Vector3f (1f, 1f, 1f),
588+ var viewRange : Float = 1f ,
589+ var translation : Vector3f = Vector3f (0f, 0f, 0f),
590+ var leftRotation : Quaternion4f = Quaternion4f (0f, 0f, 0f, 1f),
591+ var rightRotation : Quaternion4f = Quaternion4f (0f, 0f, 0f, 1f),
592+ var interpolationDelay : Int = 0 ,
593+ var transformationInterpolationDuration : Int = 0 ,
594+ var positionRotationInterpolationDuration : Int = 0 ,
495595 ) {
596+ /* *
597+ * Task for continuous rotation (if active).
598+ */
599+ internal var rotationTask: BukkitTask ? = null
600+
601+ /* *
602+ * Current rotation angle in radians for continuous rotation.
603+ */
604+ internal var currentRotationAngle: Float = 0f
496605 /* *
497606 * Vertical offset from the base location.
498607 * How far above the ground/position the display should appear.
@@ -583,6 +692,40 @@ class TextDisplayManager {
583692 fun isVisibleTo (player : Player ): Boolean {
584693 return player in viewers
585694 }
695+
696+ /* *
697+ * Starts continuous rotation around the Y-axis.
698+ * Shortcut for [TextDisplayManager.startRotation].
699+ *
700+ * @param manager The manager instance to use
701+ * @param durationTicks The duration of half a revolution in ticks (default: 100 ticks = 5 seconds)
702+ * @param scale The scale multiplier for the display during rotation (default: 1.0 = no scaling)
703+ */
704+ fun startRotation (
705+ manager : TextDisplayManager ,
706+ durationTicks : Int = 100,
707+ scale : Float = 1.0f
708+ ) {
709+ manager.startRotation(this , durationTicks, scale)
710+ }
711+
712+ /* *
713+ * Stops the continuous rotation.
714+ * Shortcut for [TextDisplayManager.stopRotation].
715+ *
716+ * @param manager The manager instance to use
717+ */
718+ fun stopRotation (manager : TextDisplayManager ) {
719+ manager.stopRotation(this )
720+ }
721+
722+ /* *
723+ * Stops any active rotation task (internal use).
724+ */
725+ internal fun stopRotation () {
726+ rotationTask?.cancel()
727+ rotationTask = null
728+ }
586729 }
587730}
588731
0 commit comments