Skip to content

Instantly share code, notes, and snippets.

@ShiftSad
Created September 30, 2023 01:11
Show Gist options
  • Save ShiftSad/61ed6ff00a14840ca85fc14ad8103c5d to your computer and use it in GitHub Desktop.
Save ShiftSad/61ed6ff00a14840ca85fc14ad8103c5d to your computer and use it in GitHub Desktop.
Utility based on NBTApi, for building DisplayEntity using Kotlin's DSL
@file:Suppress("unused")
package tech.shiftmc.townywars.api
import de.tr7zw.nbtapi.NBT
import net.kyori.adventure.text.Component
import org.bukkit.Location
import org.bukkit.Material
import org.bukkit.entity.Entity
import org.bukkit.entity.EntityType
import org.bukkit.util.Vector
import java.util.concurrent.TimeUnit
/**
* An OOP mapping of the [org.bukkit.entity.BlockDisplay] entity.
*
* Huge thanks to [MCStacker](https://mcstacker.net) for the NBT tags and documentation.
*/
class DisplayEntityBuilder(
location: Location,
) {
private val entity = location.world.spawnEntity(location, EntityType.BLOCK_DISPLAY)
/**
* X,Y,Z motion values must be output as decimals. Examples are 0.0, -0.9, 1.0 -1.0. You should set all 3 values or none at all.
*
* @param vector The motion vector.
*/
fun motion(vector: Vector) =
entity.velocity.add(vector)
/**
* If set the entity will not fall if summoned up in the air.
*
* @param gravity The gravity value.
*/
fun gravity(gravity: Boolean) =
entity.setGravity(gravity)
/**
* If set, this entity will not make sound. This attribute exists on all entities. However it may have no effect on some entities.
*
* @param silent The silent value.
*/
fun silent(silent: Boolean) {
entity.isSilent = silent
}
/**
* Number of ticks until the fire is put out. Negative values reflect how long the entity can stand in fire before burning. Default -20 when not on fire.
*
* @param time The time unit.
* @param fire The fire ticks.
*/
fun fire(time: TimeUnit, fire: Long) {
entity.fireTicks = (time.toMillis(fire) / 50).toInt()
}
/**
* The entity will appear to be on fire but is not actually on fire and is not taking damage.
*
* @param hasVisualFire The visual fire value.
*/
fun hasVisualFire(hasVisualFire: Boolean) {
entity.isVisualFire = hasVisualFire
}
/**
* Set if the entity has a glowing outline.
*
* @param glowing The glowing value.
*/
fun glowing(glowing: Boolean) {
entity.isGlowing = glowing
}
/**
* The custom name of this entity. Appears in player death messages and villager trading interfaces, as well as above the entity when your cursor is over it.
*
* @param name The custom name.
*/
fun customName(name: Component) =
entity.customName(name)
/**
* If entity has a custom name, it will always appear above them, whether or not the cursor is pointing at it. If the entity hasn't a custom name, a default name will be shown.
*
* @param visible The custom name visible value.
*/
fun customNameVisible(visible: Boolean) {
entity.isCustomNameVisible = visible
}
/**
* Maximum view range of the entity. When the distance is more than <view_range> * <entityDistanceScaling> * 64, the entity is not rendered. Defaults to 1.0.
*
* @param range The view range.
*/
fun viewRange(range: Float) =
NBT.modify(entity) { nbt ->
nbt.setFloat("view_range", range)
}
/**
* Shadow radius. Value is treated as 64 when higher than 64. If less than or equal to 0, the entity has no shadow. Defaults to 1. Interpolated.
*
* @param radius The shadow radius.
*/
fun shadowRadius(radius: Float) =
NBT.modify(entity) { nbt ->
nbt.setFloat("shadow_radius", radius)
}
/**
* Controls the opacity of the shadow as a function of distance to block below. Defaults to 1. Interpolated. Allowed values are 0 to 1.
*
* @param strength The shadow strength.
*/
fun shadowStrength(strength: Float) =
NBT.modify(entity) { nbt ->
nbt.setFloat("shadow_strength", strength)
}
/**
* Overrides the glow border color. If 0, uses the color of the team that the display entity is in. Defaults to 0.
*
* @param color The glow color.
*/
fun glowColorOverride(override: Boolean) =
NBT.modify(entity) { nbt ->
nbt.setBoolean("glow_color_override", override)
}
/**
* The maximum width of the entity. Rendering culling bounding box spans horizontally width/2 from entity position, and the part beyond will be culled. If set to 0, no culling on both vertical and horizonal directions. Defaults to 0.
*
* @param size The width size.
*/
fun width(size: Float) =
NBT.modify(entity) { nbt ->
nbt.setFloat("width", size)
}
/**
* The maximum height of the entity. Rendering culling bounding box spans vertically y to y+height, and the part beyond will be culled. If set to 0, no culling on both vertical and horizonal directions. Defaults to 0.
*
* @param size The height size.
*/
fun height(size: Float) =
NBT.modify(entity) { nbt ->
nbt.setFloat("height", size)
}
/**
* Controls if this entity should pivot to face player when rendered. It can be fixed (both vertical and horizontal angles are fixed), vertical (faces player around vertical axis), horizontal (pivots around horizonal axis), and center (pivots around center point). Defaults to fixed.
*
* @param billboard The billboard value.
*/
fun billboard(billboard: BillBoard) =
NBT.modify(entity) { nbt ->
nbt.setString("billboard", billboard.name.lowercase())
}
/**
* Interpolation start time. If less than 0, sets to the current game time.
*
* @param time The time unit.
* @param start The start time.
*/
fun startInterpolation(time: TimeUnit, start: Int) =
NBT.modify(entity) { nbt ->
nbt.setInteger("start_interpolation", (time.toMillis(start.toLong()) / 50).toInt())
}
/**
* The duration in ticks it will take for the transformation to end.
*
* @param time The time unit.
* @param duration The duration.
*/
fun interpolationDuration(time: TimeUnit, duration: Int) =
NBT.modify(entity) { nbt ->
nbt.setInteger("interpolation_duration", (time.toMillis(duration.toLong()) / 50).toInt())
}
/**
* Initial rotation. This tag corresponds to the right-singular vector matrix after the matrix singular value decomposition.
*
* This tag has quaternion form and angle-axis form. Only quaternion form are used when saving entities. If quaternion form is used to represent rotation, this tag is a list of float numbers with 4 elements: Axis vector with 3 elements(x,y,z) and angle (a).
*
* @param rotation The rotation. 4 elements: Axis vector with 3 elements(x,y,z) and angle (a).
*/
fun rightRotation(rotation: FloatArray) =
NBT.modify(entity) { nbt ->
val transformation = nbt.getOrCreateCompound("transformation")
transformation.getFloatList("right_rotation").apply {
this[0] = rotation[0]
this[1] = rotation[1]
this[2] = rotation[2]
this[3] = rotation[3]
}
}
/**
* Rotates the model again. This tag corresponds to the left-singular vector matrix after the matrix singular value decomposition.
*
* This tag has quaternion form and angle-axis form. Only quaternion form are used when saving entities. If quaternion form is used to represent rotation, this tag is a list of float numbers with 4 elements: Axis vector with 3 elements(x,y,z) and angle (a).
*
* @param rotation The rotation. 4 elements: Axis vector with 3 elements(x,y,z) and angle (a).
*/
fun leftRotation(rotation: FloatArray) =
NBT.modify(entity) { nbt ->
val transformation = nbt.getOrCreateCompound("transformation")
transformation.getFloatList("left_rotation").apply {
this[0] = rotation[0]
this[1] = rotation[1]
this[2] = rotation[2]
this[3] = rotation[3]
}
}
/**
* Translation transformation. This tag corresponds to the last column in the matrix form. This tag is a list of float numbers with 3 elements (x,y,z).
*
* @param translation The translation. 3 elements (x,y,z).
*/
fun translation(translation: FloatArray) =
NBT.modify(entity) { nbt ->
val transformation = nbt.getOrCreateCompound("transformation")
transformation.getFloatList("translation").apply {
this[0] = translation[0]
this[1] = translation[1]
this[2] = translation[2]
}
}
/**
* Scale the model centered on the origin. This tag corresponds to the singular values of the matrix after singular value decomposition. This tag is a list of float numbers with 3 elements (x,y,z).
*
* @param scale The scale. 3 elements (x,y,z).
*/
fun scale(scale: FloatArray) =
NBT.modify(entity) { nbt ->
val transformation = nbt.getOrCreateCompound("transformation")
transformation.getFloatList("scale").apply {
this[0] = scale[0]
this[1] = scale[1]
this[2] = scale[2]
}
}
/**
* Sets the transformation of the entity. A combination of all the transformation methods.
*
* @param leftRotation The left rotation. Check [leftRotation].
* @param rightRotation The right rotation. Check [rightRotation].
* @param translation The translation. Check [translation].
* @param scale The scale. Check [scale].
*/
fun transformation(
leftRotation: FloatArray,
rightRotation: FloatArray,
translation: FloatArray,
scale: FloatArray
) {
leftRotation(leftRotation)
rightRotation(rightRotation)
translation(translation)
scale(scale)
}
/**
* The block to display and its block state options if present.
*
* @param material The material. Example: "minecraft:stone".
*/
fun blockState(material: String) =
NBT.modify(entity) { nbt ->
val compound = nbt.getOrCreateCompound("block_state")
compound.setString("Name", material)
}
/**
* The block to display and its block state options if present.
*
* @param material The material. Example: "minecraft:stone".
*/
fun blockState(material: Material) =
NBT.modify(entity) { nbt ->
val compound = nbt.getOrCreateCompound("block_state")
compound.setString("Name", "minecraft:${material.name.lowercase()}")
}
fun build(): Entity {
// -- Checks before building. --
NBT.get(entity) {
if (!it.hasTag("block_state"))
throw IllegalStateException("Block state is not set.")
if (!it.hasTag("transformation"))
throw IllegalStateException("Transformation is not set.")
if (it.getCompound("transformation")?.hasTag("scale") == false)
throw IllegalStateException("Scale is not set.")
}
return entity
}
}
/**
* The billboard type. Used in [DisplayEntityBuilder.billboard].
*/
enum class BillBoard {
NONE,
FIXED,
VERTICAL,
HORIZONTAL,
CENTER
}
/**
* Creates a new [DisplayEntityBuilder] and returns it.
*/
fun displayEntity(location: Location, block: DisplayEntityBuilder.() -> Unit): DisplayEntityBuilder {
val builder = DisplayEntityBuilder(location)
builder.block()
return builder
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment