diff --git a/core/src/main/kotlin/org/holoeasy/HoloEasy.kt b/core/src/main/kotlin/org/holoeasy/HoloEasy.kt new file mode 100644 index 0000000..b5d14b4 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/HoloEasy.kt @@ -0,0 +1,31 @@ +package org.holoeasy + + +import org.bukkit.plugin.Plugin +import org.holoeasy.pool.HologramPool +import org.holoeasy.pool.IHologramPool +import org.holoeasy.pool.InteractiveHologramPool + +object HoloEasy { + + @JvmField + var useLastSupportedVersion: Boolean = false + + @JvmStatic + fun startPool(plugin: Plugin, spawnDistance: Double): IHologramPool { + val simplepool = HologramPool(plugin, spawnDistance) + return simplepool + } + + @JvmStatic + fun startInteractivePool( + plugin: Plugin, spawnDistance: Double, + minHitDistance: Float, maxHitDistance: Float + ): IHologramPool { + val simplepool = HologramPool(plugin, spawnDistance) + val interactivepool = InteractiveHologramPool(simplepool, minHitDistance, maxHitDistance) + return interactivepool + } + + +} diff --git a/core/src/main/kotlin/org/holoeasy/builder/HologramBuilder.java b/core/src/main/kotlin/org/holoeasy/builder/HologramBuilder.java new file mode 100644 index 0000000..173e4f8 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/builder/HologramBuilder.java @@ -0,0 +1,85 @@ +package org.holoeasy.builder; + +import org.holoeasy.builder.interfaces.HologramConfigGroup; +import org.holoeasy.builder.interfaces.HologramSetupGroup; +import org.holoeasy.config.HologramKey; +import org.bukkit.Location; +import org.bukkit.inventory.ItemStack; +import org.holoeasy.hologram.Hologram; +import org.holoeasy.line.ILine; +import org.holoeasy.line.ITextLine; +import org.holoeasy.pool.IHologramPool; +import org.holoeasy.reactive.MutableState; +import org.jetbrains.annotations.NotNull; + +public class HologramBuilder { + + static Service getInstance() { + return Service.INSTANCE; + } + + public static Hologram hologram( + @NotNull HologramKey key, @NotNull Location location, @NotNull HologramSetupGroup setupGroup) { + HologramConfig holoConfig = new HologramConfig(key, location); + getInstance().getStaticHologram().set(holoConfig); + setupGroup.setup(); + getInstance().getStaticHologram().remove(); + + Hologram holo = new Hologram(key, holoConfig.location, holoConfig.loader); + holo.load(holoConfig.lines.toArray(new ILine[0])); + return holo; + } + + public static void config(@NotNull HologramConfigGroup configGroup) { + getInstance().config(configGroup); + } + + + public static void textline(@NotNull String text, @NotNull Object... args) { + getInstance().textline( + text, + false, + null, + null, + args.length == 0 ? null : args + ); + } + + public static ITextLine clickable(@NotNull String text, @NotNull Object... args) { + return getInstance().textline( + text, + true, + null, + null, + args.length == 0 ? null : args + + ); + } + + public static ITextLine clickable(@NotNull String text, float minHitDistance, float maxHitDistance, + @NotNull Object... args) { + return getInstance().textline( + text, + true, + minHitDistance, + maxHitDistance, + args.length == 0 ? null : args + ); + } + + public static void item(@NotNull ItemStack block) { + getInstance().itemline(block); + } + + public static void item(@NotNull MutableState block) { + getInstance().itemlineMutable(block); + } + + public static void customline(@NotNull ILine customLine) { + getInstance().customLine(customLine); + } + + public static MutableState mutableStateOf(@NotNull T initialValue) { + return new MutableState<>(initialValue); + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/holoeasy/builder/HologramConfig.java b/core/src/main/kotlin/org/holoeasy/builder/HologramConfig.java new file mode 100644 index 0000000..36089a3 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/builder/HologramConfig.java @@ -0,0 +1,29 @@ +package org.holoeasy.builder; + +import org.holoeasy.config.HologramKey; +import org.bukkit.Location; +import org.holoeasy.hologram.IHologramLoader; +import org.holoeasy.hologram.TextBlockStandardLoader; +import org.holoeasy.line.ILine; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + + +public class HologramConfig { + + @NotNull + final HologramKey key; + @NotNull + final Location location; + final List> lines = new ArrayList<>(); + public IHologramLoader loader = new TextBlockStandardLoader(); + + HologramConfig(@NotNull HologramKey key, @NotNull Location location) { + this.key = key; + this.location = location; + } + + +} diff --git a/core/src/main/kotlin/org/holoeasy/builder/Service.kt b/core/src/main/kotlin/org/holoeasy/builder/Service.kt new file mode 100644 index 0000000..7692b59 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/builder/Service.kt @@ -0,0 +1,69 @@ +package org.holoeasy.builder + + +import org.bukkit.inventory.ItemStack +import org.holoeasy.builder.interfaces.HologramConfigGroup +import org.holoeasy.hologram.TextBlockStandardLoader +import org.holoeasy.line.* +import org.holoeasy.reactive.MutableState +import kotlin.math.min + +object Service { + + val staticHologram: ThreadLocal = ThreadLocal() + + private fun getStaticHolo(): HologramConfig { + val holo = staticHologram.get() ?: throw RuntimeException("You must call config() inside hologram block") + return holo + } + + fun config(configGroup: HologramConfigGroup) { + val config = getStaticHolo() + + configGroup.configure(config) + + + config.loader = config.loader ?: TextBlockStandardLoader() + } + + @JvmOverloads + fun textline( + text: String, clickable: Boolean = false, minHitDistance: Float? = null, + maxHitDistance: Float? = null, args: Array<*>? = null + ) : ITextLine { + val holo = getStaticHolo() + + if (minHitDistance == null || maxHitDistance == null) { + if(holo.key.pool == null && clickable) { + throw IllegalStateException("This hologram is not in a pool,so use the method #clickable(text, minHitDistance, maxHitDistance)") + } + + val textLine = TextLine(holo.key.plugin, text, clickable = clickable, args = args) + holo.lines.add(textLine) + return textLine + + } + val textLine = TextLine(holo.key.plugin, text, clickable = false, args = args) + val clickableTextLine = ClickableTextLine(textLine, minHitDistance, maxHitDistance) + holo.lines.add(clickableTextLine) + return clickableTextLine + } + + fun itemline(block: ItemStack) { + val holo = getStaticHolo() + val blockline = BlockLine(holo.key.plugin, block) + holo.lines.add(blockline) + } + + fun itemlineMutable(block: MutableState) { + val holo = getStaticHolo() + val blockline = BlockLine(holo.key.plugin, block) + holo.lines.add(blockline) + } + + fun customLine(customLine: ILine<*>) { + val holo = getStaticHolo() + holo.lines.add(customLine) + } + +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/holoeasy/builder/interfaces/HologramConfigGroup.java b/core/src/main/kotlin/org/holoeasy/builder/interfaces/HologramConfigGroup.java new file mode 100644 index 0000000..d56646b --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/builder/interfaces/HologramConfigGroup.java @@ -0,0 +1,9 @@ +package org.holoeasy.builder.interfaces; + +import org.holoeasy.builder.HologramConfig; +import org.jetbrains.annotations.NotNull; + +@FunctionalInterface +public interface HologramConfigGroup { + void configure(@NotNull HologramConfig context); +} diff --git a/core/src/main/kotlin/org/holoeasy/builder/interfaces/HologramSetupGroup.java b/core/src/main/kotlin/org/holoeasy/builder/interfaces/HologramSetupGroup.java new file mode 100644 index 0000000..8e61aa8 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/builder/interfaces/HologramSetupGroup.java @@ -0,0 +1,7 @@ +package org.holoeasy.builder.interfaces; + +@FunctionalInterface +public interface HologramSetupGroup { + void setup(); + +} diff --git a/core/src/main/kotlin/org/holoeasy/config/HologramKey.java b/core/src/main/kotlin/org/holoeasy/config/HologramKey.java new file mode 100644 index 0000000..11d883b --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/config/HologramKey.java @@ -0,0 +1,68 @@ +package org.holoeasy.config; + +import org.bukkit.plugin.Plugin; +import org.holoeasy.pool.IHologramPool; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; + +public class HologramKey { + + private final Plugin plugin; + private final String id; + + private final IHologramPool pool; + + + public HologramKey(@NotNull Plugin plugin, @NotNull String id, @Nullable IHologramPool pool) { + this.plugin = plugin; + this.id = id; + this.pool = pool; + } + + public HologramKey(@NotNull Plugin plugin, @NotNull String id) { + this(plugin, id, null); + } + + public HologramKey(@NotNull IHologramPool pool, @NotNull String id) { + this(pool.getPlugin(), id, pool); + } + + @NotNull + public Plugin getPlugin() { + return plugin; + } + + @NotNull + public String getId() { + return id; + } + + @Nullable + public IHologramPool getPool() { + return pool; + } + + @Override + public int hashCode() { + return Objects.hash(plugin, id, pool); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + HologramKey that = (HologramKey) o; + return Objects.equals(plugin, that.plugin) && Objects.equals(id, that.id) && Objects.equals(pool, that.pool); + } + + @Override + public String toString() { + return "HologramKey{" + + "plugin=" + plugin + + ", id='" + id + '\'' + + ", pool=" + pool + + '}'; + } +} diff --git a/core/src/main/kotlin/org/holoeasy/ext/DoubleExt.kt b/core/src/main/kotlin/org/holoeasy/ext/DoubleExt.kt new file mode 100644 index 0000000..651fbd2 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/ext/DoubleExt.kt @@ -0,0 +1,9 @@ +package org.holoeasy.ext + +import kotlin.math.floor + +val Double.compressAngle: Byte + get() = (this * 256f / 360f).toInt().toByte() + +val Double.fixCoordinate: Int + get() = floor(this * 32.0).toInt() diff --git a/core/src/main/kotlin/org/holoeasy/ext/ItemStackExt.kt b/core/src/main/kotlin/org/holoeasy/ext/ItemStackExt.kt new file mode 100644 index 0000000..da025ef --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/ext/ItemStackExt.kt @@ -0,0 +1,9 @@ +package org.holoeasy.ext + +import com.comphenix.protocol.wrappers.BukkitConverters +import org.bukkit.inventory.ItemStack + + +fun ItemStack.bukkitGeneric() : Any { + return BukkitConverters.getItemStackConverter().getGeneric(this) +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/holoeasy/ext/PacketContainerExt.kt b/core/src/main/kotlin/org/holoeasy/ext/PacketContainerExt.kt new file mode 100644 index 0000000..96a544d --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/ext/PacketContainerExt.kt @@ -0,0 +1,37 @@ +package org.holoeasy.ext + +import com.comphenix.protocol.ProtocolLibrary +import com.comphenix.protocol.events.PacketContainer +import com.comphenix.protocol.utility.MinecraftVersion +import com.comphenix.protocol.wrappers.WrappedDataValue +import com.comphenix.protocol.wrappers.WrappedDataWatcher +import com.google.common.collect.Lists +import org.bukkit.entity.Player +import java.util.* + +fun PacketContainer.send(player: Player) { + ProtocolLibrary.getProtocolManager().sendServerPacket(player, this) +} + +operator fun PacketContainer.invoke(player: Player) { + send(player) +} + +fun PacketContainer.parse119(watcher: WrappedDataWatcher) { + if (MinecraftVersion.getCurrentVersion().isAtLeast(MinecraftVersion("1.19.3"))) { + val wrappedDataValueList: MutableList = Lists.newArrayList() + watcher.watchableObjects.stream().filter(Objects::nonNull).forEach { entry -> + val dataWatcherObject: WrappedDataWatcher.WrappedDataWatcherObject = entry.watcherObject + wrappedDataValueList.add( + WrappedDataValue( + dataWatcherObject.index, + dataWatcherObject.serializer, + entry.rawValue + ) + ) + } + dataValueCollectionModifier.write(0, wrappedDataValueList) + } else { + watchableCollectionModifier.write(0, watcher.watchableObjects) + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/holoeasy/ext/StructureModifierExt.kt b/core/src/main/kotlin/org/holoeasy/ext/StructureModifierExt.kt new file mode 100644 index 0000000..3c39ff7 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/ext/StructureModifierExt.kt @@ -0,0 +1,7 @@ +package org.holoeasy.ext + +import com.comphenix.protocol.reflect.StructureModifier + +operator fun StructureModifier.set(index: Int, value: T) { + write(index, value) +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/holoeasy/ext/WrappedDataWatcherExt.kt b/core/src/main/kotlin/org/holoeasy/ext/WrappedDataWatcherExt.kt new file mode 100644 index 0000000..e5c3317 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/ext/WrappedDataWatcherExt.kt @@ -0,0 +1,60 @@ +package org.holoeasy.ext + +import com.comphenix.protocol.wrappers.WrappedChatComponent +import com.comphenix.protocol.wrappers.WrappedDataWatcher +import com.comphenix.protocol.wrappers.WrappedDataWatcher.WrappedDataWatcherObject +import org.bukkit.inventory.ItemStack +import org.holoeasy.util.BOOL_SERIALIZER +import org.holoeasy.util.BYTE_SERIALIZER +import org.holoeasy.util.ITEM_SERIALIZER +import org.holoeasy.util.STRING_SERIALIZER +import java.util.* + + +fun WrappedDataWatcher.setByte(index: Int, value: Byte) { + val obj = WrappedDataWatcherObject( + index, + BYTE_SERIALIZER + ) + this.setObject(obj, value) +} + +fun WrappedDataWatcher.setString(index: Int, value: String) { + this.setObject( + WrappedDataWatcherObject( + index, + STRING_SERIALIZER + ), value + ) +} + +fun WrappedDataWatcher.setBool(index: Int, value: Boolean) { + val obj = WrappedDataWatcherObject( + index, + BOOL_SERIALIZER + ) + this.setObject(obj, value) +} + +fun WrappedDataWatcher.setVectorSerializer(index: Int, value: Any) { + val obj = WrappedDataWatcherObject( + index, + WrappedDataWatcher.Registry.getVectorSerializer() + ) + this.setObject(obj, value) +} + +fun WrappedDataWatcher.setChatComponent(index: Int, value: String) { + val opt: Optional<*> = Optional.of(WrappedChatComponent.fromChatMessage(value)[0].handle) + this.setObject( + WrappedDataWatcherObject( + index, + WrappedDataWatcher.Registry.getChatComponentSerializer(true) + ), opt + ) +} + +fun WrappedDataWatcher.setItemStack(index: Int, value: ItemStack) { + val obj = WrappedDataWatcherObject(index, ITEM_SERIALIZER) + this.setObject(obj, value.bukkitGeneric()) +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/holoeasy/hologram/HideEvent.java b/core/src/main/kotlin/org/holoeasy/hologram/HideEvent.java new file mode 100644 index 0000000..d789bbb --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/hologram/HideEvent.java @@ -0,0 +1,11 @@ +package org.holoeasy.hologram; + +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +@FunctionalInterface +public interface HideEvent { + + void onHide(@NotNull Player player); + +} diff --git a/core/src/main/kotlin/org/holoeasy/hologram/Hologram.kt b/core/src/main/kotlin/org/holoeasy/hologram/Hologram.kt new file mode 100644 index 0000000..cf19c1e --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/hologram/Hologram.kt @@ -0,0 +1,92 @@ +package org.holoeasy.hologram + +import org.holoeasy.config.HologramKey +import org.bukkit.Location +import org.bukkit.entity.Player +import org.holoeasy.line.ILine +import java.util.* +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.CopyOnWriteArrayList + +class Hologram(val key: HologramKey, location: Location, val loader: IHologramLoader) { + init { + key.pool?.takeCareOf(key, this) + } + + var location: Location = location + private set + + private val hLines: MutableList> = + CopyOnWriteArrayList() // writes are slow and Iterators are fast and consistent. + + val lines: MutableList> + get() = hLines + + val seeingPlayers: MutableSet = ConcurrentHashMap.newKeySet() // faster writes + + private var showEvent: ShowEvent? = null + private var hideEvent : HideEvent? = null + + fun onShow(showEvent: ShowEvent) : Hologram { + this.showEvent = showEvent + return this + } + + fun onHide(hideEvent: HideEvent) : Hologram { + this.hideEvent = hideEvent + return this + } + + fun load(vararg lines: ILine<*>) { + hLines.clear() + lines.forEach { it.pvt.hologram = this } + loader.load(this, lines) + } + + fun teleport(to: Location) { + this.location = to.clone() + loader.teleport(this) + } + + fun isShownFor(player: Player): Boolean { + return seeingPlayers.contains(player) + } + + fun show(player: Player) { + seeingPlayers.add(player) + for (line in this.hLines) { + line.show(player) + } + + showEvent?.onShow(player) + } + + fun hide(player: Player) { + for (line in this.hLines) { + line.hide(player) + } + seeingPlayers.remove(player) + + hideEvent?.onHide(player) + } + + override fun hashCode(): Int { + return key.hashCode() + } + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + if (other == null || javaClass != other.javaClass) { + return false + } + val hologram = other as Hologram + + return Objects.equals(key, hologram.key) + } + + override fun toString(): String { + return "Hologram[key=${key.id}]" + } +} diff --git a/core/src/main/kotlin/org/holoeasy/hologram/IHologramLoader.kt b/core/src/main/kotlin/org/holoeasy/hologram/IHologramLoader.kt new file mode 100644 index 0000000..5de7083 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/hologram/IHologramLoader.kt @@ -0,0 +1,10 @@ +package org.holoeasy.hologram + +import org.holoeasy.line.ILine + + +interface IHologramLoader { + fun load(hologram: Hologram, lines: Array>) + + fun teleport(hologram: Hologram) +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/holoeasy/hologram/ShowEvent.java b/core/src/main/kotlin/org/holoeasy/hologram/ShowEvent.java new file mode 100644 index 0000000..89d8e33 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/hologram/ShowEvent.java @@ -0,0 +1,11 @@ +package org.holoeasy.hologram; + +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +@FunctionalInterface +public interface ShowEvent { + + void onShow(@NotNull Player player); + +} diff --git a/core/src/main/kotlin/org/holoeasy/hologram/SingletonLoader.kt b/core/src/main/kotlin/org/holoeasy/hologram/SingletonLoader.kt new file mode 100644 index 0000000..df7c8d0 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/hologram/SingletonLoader.kt @@ -0,0 +1,26 @@ +package org.holoeasy.hologram + +import org.holoeasy.line.ILine + + +class SingletonLoader : IHologramLoader { + override fun load(hologram: Hologram, lines: Array>) { + if (lines.size > 1) { + throw RuntimeException("Hologram '${hologram.key}' has more than 1 line.") + } + + val cloned = hologram.location.clone() + + val line: ILine<*> = lines[0] + + line.setLocation(cloned) + hologram.lines.add(line) + } + + override fun teleport(hologram: Hologram) { + val line: ILine<*> = hologram.lines[0] + + line.setLocation(hologram.location.clone()) + hologram.seeingPlayers.forEach(line::teleport) + } +} diff --git a/core/src/main/kotlin/org/holoeasy/hologram/TextBlockStandardLoader.kt b/core/src/main/kotlin/org/holoeasy/hologram/TextBlockStandardLoader.kt new file mode 100644 index 0000000..58423b7 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/hologram/TextBlockStandardLoader.kt @@ -0,0 +1,90 @@ +package org.holoeasy.hologram + +import org.holoeasy.line.ILine +import kotlin.math.abs + +class TextBlockStandardLoader : IHologramLoader { + override fun load(hologram: Hologram, lines: Array>) { + val cloned = hologram.location.clone() + + if (lines.size == 1) { + val line: ILine<*> = lines[0] + + line.setLocation(cloned) + hologram.lines.add(line) + return + } + + // reverse A - B - C to C - B - A + lines.reverse() + + cloned.subtract(0.0, 0.28, 0.0) + + for (j in lines.indices) { + val line: ILine<*> = lines[j] + var up = 0.28 + + if (j > 0) { + val before: ILine.Type = lines[j - 1].type + when (before) { + ILine.Type.BLOCK_LINE -> up = -1.5 + ILine.Type.EXTERNAL -> {} + ILine.Type.TEXT_LINE -> {} + ILine.Type.CLICKABLE_TEXT_LINE -> {} + } + } + + when (line.type) { + ILine.Type.TEXT_LINE, ILine.Type.CLICKABLE_TEXT_LINE -> { + line.setLocation(cloned.add(0.0, up, 0.0).clone()) + hologram.lines.add(0, line) + } + + ILine.Type.BLOCK_LINE -> { + line.setLocation(cloned.add(0.0, 0.6, 0.0).clone()) + hologram.lines.add(0, line) + } + + else -> throw RuntimeException("This method load does not support line type " + line.type.name) + } + } + } + + override fun teleport(hologram: Hologram) { + val lines: List> = hologram.lines + val firstLine: ILine<*> = lines[0] + // Obtain the Y position of the first line and then calculate the distance to all lines to maintain this distance + val baseY: Double = firstLine.location?.y ?: throw RuntimeException("First line has not a location") + // Get position Y where to teleport the first line + var destY = (hologram.location.y - 0.28) + + destY += when (firstLine.type) { + ILine.Type.TEXT_LINE, ILine.Type.CLICKABLE_TEXT_LINE -> 0.28 + else -> 0.6 + } + + // Teleport the first line + this.teleportLine(hologram, destY, firstLine) + var tempLine: ILine<*> + for (j in 1 until lines.size) { + tempLine = lines[j] + /* + Teleport from the second line onwards. + The final height is found by adding to that of the first line the difference that was present when it was already spawned + */ + this.teleportLine( + hologram, destY + abs( + baseY - + (tempLine.location?.y ?: throw RuntimeException("Missing location of line $tempLine")) + ), tempLine + ) + } + } + + private fun teleportLine(hologram: Hologram, destY: Double, tempLine: ILine<*>) { + val dest = hologram.location.clone() + dest.y = destY + tempLine.setLocation(dest) + hologram.seeingPlayers.forEach(tempLine::teleport) + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/holoeasy/hologram/TextSequentialLoader.kt b/core/src/main/kotlin/org/holoeasy/hologram/TextSequentialLoader.kt new file mode 100644 index 0000000..7b8d445 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/hologram/TextSequentialLoader.kt @@ -0,0 +1,41 @@ +package org.holoeasy.hologram + + +import org.holoeasy.line.ILine +import org.holoeasy.line.ITextLine +import org.jetbrains.annotations.ApiStatus + +@ApiStatus.Experimental +class TextSequentialLoader : IHologramLoader { + override fun load(hologram: Hologram, lines: Array>) { + set(hologram, lines, true) + } + + override fun teleport(hologram: Hologram) { + set(hologram, hologram.lines.toTypedArray(), false) + // TODO: When teleporting, the holograms unexpectedly become distant. Understand why. + } + + private fun set(hologram: Hologram, lines: Array>, add: Boolean) { + val cloned = hologram.location.clone() + for (line in lines) { + when (line.type) { + ILine.Type.TEXT_LINE, ILine.Type.CLICKABLE_TEXT_LINE -> { + val tL = (line as ITextLine).textLine + + // add to lines + tL.setLocation(cloned.clone()) + + if (add) { + hologram.lines.add(0, tL) + } else { + hologram.seeingPlayers.forEach { tL.teleport(it) } + } + cloned.z += 0.175 * tL.obj.length + } + + else -> throw RuntimeException("This method load supports only TextLine & TextALine & ClickableTextLine.") + } + } + } +} diff --git a/core/src/main/kotlin/org/holoeasy/line/BlockLine.kt b/core/src/main/kotlin/org/holoeasy/line/BlockLine.kt new file mode 100644 index 0000000..68c8129 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/line/BlockLine.kt @@ -0,0 +1,79 @@ +package org.holoeasy.line + + +import org.bukkit.Location +import org.bukkit.entity.EntityType +import org.bukkit.entity.Player +import org.bukkit.inventory.ItemStack +import org.bukkit.plugin.Plugin +import org.holoeasy.ext.send +import org.holoeasy.packet.IPacket +import org.holoeasy.reactive.MutableState +import org.holoeasy.util.VersionEnum +import org.holoeasy.util.VersionUtil + +class BlockLine(plugin: Plugin, obj: MutableState) : ILine { + + init { + if(VersionUtil.isCompatible(VersionEnum.V1_8)) { + throw IllegalStateException("This version does not support item lines") + } + } + + private val line: Line = Line(plugin, EntityType.DROPPED_ITEM) + private val resetVelocity = IPacket.get(IPacket.Type.VELOCITY).velocity(line.entityID, 0, 0,0) + + private val _mutableStateOf = obj + + private var firstRender = true + + constructor(plugin: Plugin, obj: ItemStack) : this(plugin, MutableState(obj)){ + } + + override val plugin: Plugin + get() = line.plugin + + override val type: ILine.Type + get() = ILine.Type.BLOCK_LINE + + override val entityId: Int + get() = line.entityID + + override val location: Location? + get() = line.location + + override var obj : ItemStack + get() = _mutableStateOf.get() + set(value) = _mutableStateOf.set(value) + + override var pvt = ILine.PrivateConfig(this) + + override fun setLocation(value: Location) { + line.location = value + } + + override fun hide(player: Player) { + line.destroy(player) + } + + override fun teleport(player: Player) { + line.teleport(player) + } + + override fun show(player: Player) { + line.spawn(player) + this.update(player) + + resetVelocity.send(player) + + if(firstRender) { + firstRender = false + _mutableStateOf.addObserver(pvt) + } + } + + override fun update(player: Player) { + IPacket.get(IPacket.Type.METADATA_ITEM) + .metadata(entityId, obj).send(player) + } +} diff --git a/core/src/main/kotlin/org/holoeasy/line/ClickEvent.java b/core/src/main/kotlin/org/holoeasy/line/ClickEvent.java new file mode 100644 index 0000000..1fe2f3f --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/line/ClickEvent.java @@ -0,0 +1,11 @@ +package org.holoeasy.line; + +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +@FunctionalInterface +public interface ClickEvent { + + void onClick(@NotNull Player player); + +} diff --git a/core/src/main/kotlin/org/holoeasy/line/ClickableTextLine.kt b/core/src/main/kotlin/org/holoeasy/line/ClickableTextLine.kt new file mode 100644 index 0000000..db84a32 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/line/ClickableTextLine.kt @@ -0,0 +1,127 @@ +package org.holoeasy.line + +import org.bukkit.Bukkit +import org.bukkit.Location +import org.bukkit.entity.Player +import org.bukkit.event.EventHandler +import org.bukkit.event.Listener +import org.bukkit.event.block.Action +import org.bukkit.event.player.PlayerInteractEvent +import org.bukkit.plugin.Plugin +import org.holoeasy.util.AABB +import org.holoeasy.util.AABB.Vec3D.Companion.fromLocation + +class ClickableTextLine(private val line: TextLine, minHitDistance: Float, maxHitDistance: Float) : Listener, + ITextLine { + private val minHitDistance: Float + private val maxHitDistance: Float + private var hitbox: AABB? = null + + private val playersClickable = mutableSetOf() + + init { + require(!(minHitDistance < 0)) { "minHitDistance must be positive" } + require(!(maxHitDistance > 120)) { "maxHitDistance cannot be greater than 120" } + this.minHitDistance = minHitDistance + this.maxHitDistance = maxHitDistance + + if (line.location != null) { + this.updateHitBox() + } + + Bukkit.getPluginManager().registerEvents(this, line.plugin) + } + + override val clickable: Boolean + get() = false + + override val textLine: TextLine + get() = line + + override val args: Array<*>? + get() = textLine.args + + + override fun parse(player: Player): String { + return line.parse(player) + } + + override fun onClick(clickEvent: ClickEvent) { + line.onClick(clickEvent) + } + + override val plugin: Plugin + get() = line.plugin + + override val type: ILine.Type + get() = ILine.Type.CLICKABLE_TEXT_LINE + + override val entityId: Int + get() = line.entityId + + override val location: Location? + get() = line.location + + override var obj: String + get() = line.obj + set(value) { + line.obj = value + } + + override var pvt = ILine.PrivateConfig(this) + + override fun setLocation(value: Location) { + line.setLocation(value) + this.updateHitBox() + } + + override fun hide(player: Player) { + line.hide(player) + + playersClickable.remove(player.entityId) + } + + override fun teleport(player: Player) { + line.teleport(player) + } + + override fun show(player: Player) { + line.show(player) + + playersClickable.add(player.entityId) + } + + override fun update(player: Player) { + line.update(player) + } + + @EventHandler + fun handleInteract(e: PlayerInteractEvent) { + val player = e.player + if (e.action != Action.LEFT_CLICK_AIR) { + return + } + if (hitbox == null) { + return + } + + if (!playersClickable.contains(player.entityId)) { + return + } + + val intersects = hitbox!!.intersectsRay(AABB.Ray3D(player.eyeLocation), minHitDistance, maxHitDistance) ?: return + line.clickEvent?.onClick(player) + } + + private fun updateHitBox() { + val chars = obj.length.toDouble() + val size = 0.105 + val dist = size * (chars / 2.0) + + hitbox = AABB( + AABB.Vec3D(-dist, -0.040, -dist), + AABB.Vec3D(dist, +0.040, dist) + ) + hitbox!!.translate(fromLocation(location!!.clone().add(0.0, 2.35, 0.0))) + } +} diff --git a/core/src/main/kotlin/org/holoeasy/line/ILine.kt b/core/src/main/kotlin/org/holoeasy/line/ILine.kt new file mode 100644 index 0000000..d03d303 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/line/ILine.kt @@ -0,0 +1,59 @@ +package org.holoeasy.line + +import org.bukkit.Location +import org.bukkit.entity.Player +import org.bukkit.plugin.Plugin +import org.holoeasy.hologram.Hologram +import org.holoeasy.reactive.Observer +import org.jetbrains.annotations.ApiStatus + +interface ILine { + + data class PrivateConfig(private val line: ILine<*>) : Observer { + + lateinit var hologram: Hologram + override fun observerUpdate() { + hologram.let { + line.update(it.seeingPlayers) + } + } + } + + val plugin: Plugin + + val type: Type + + val entityId: Int + + val location: Location? + + var obj: T + + var pvt : PrivateConfig + + fun setLocation(value: Location) + + fun hide(player: Player) + + fun teleport(player: Player) + + fun show(player: Player) + + fun update(player: Player) + + fun update(seeingPlayers: Collection) { + for (player in seeingPlayers) { + update(player) + } + } + + enum class Type { + EXTERNAL, + TEXT_LINE, + @ApiStatus.Experimental + CLICKABLE_TEXT_LINE, + + BLOCK_LINE, + + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/holoeasy/line/ITextLine.kt b/core/src/main/kotlin/org/holoeasy/line/ITextLine.kt new file mode 100644 index 0000000..6427de0 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/line/ITextLine.kt @@ -0,0 +1,17 @@ +package org.holoeasy.line + +import org.bukkit.entity.Player + +interface ITextLine : ILine { + + val clickable: Boolean + + val textLine: TextLine + + val args: Array<*>? + + fun parse(player: Player): String + + fun onClick(clickEvent: ClickEvent) + +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/holoeasy/line/Line.kt b/core/src/main/kotlin/org/holoeasy/line/Line.kt new file mode 100644 index 0000000..e193462 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/line/Line.kt @@ -0,0 +1,41 @@ +package org.holoeasy.line + +import com.comphenix.protocol.events.PacketContainer +import org.bukkit.Location +import org.bukkit.entity.EntityType +import org.bukkit.entity.Player +import org.bukkit.plugin.Plugin +import org.holoeasy.ext.invoke +import org.holoeasy.packet.IPacket +import java.util.* +import java.util.concurrent.atomic.AtomicInteger + +class Line(val plugin: Plugin, private val entityType: EntityType, var location: Location? = null) { + + companion object { + val IDs_COUNTER = AtomicInteger(Random().nextInt()) + } + + val entityID: Int = IDs_COUNTER.getAndIncrement() + private val entityDestroyPacket : PacketContainer = IPacket.get(IPacket.Type.DELETE).delete(entityID) + + fun destroy(player: Player) { + entityDestroyPacket(player) + } + + fun spawn(player: Player) { + val packet = IPacket.get(IPacket.Type.SPAWN) + .spawn(entityID, entityType, location ?: throw RuntimeException("Forgot the location?"), plugin) + packet(player) + } + + fun teleport(player: Player) { + + val packet = IPacket.get(IPacket.Type.TELEPORT) + .teleport(entityID, location ?: throw RuntimeException("Forgot the location?")) + packet(player) + + } + + +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/holoeasy/line/TextLine.kt b/core/src/main/kotlin/org/holoeasy/line/TextLine.kt new file mode 100644 index 0000000..fda70c0 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/line/TextLine.kt @@ -0,0 +1,138 @@ +package org.holoeasy.line + +import org.bukkit.Location +import org.bukkit.entity.EntityType +import org.bukkit.entity.Player +import org.bukkit.plugin.Plugin +import org.holoeasy.ext.send +import org.holoeasy.packet.IPacket +import org.holoeasy.reactive.MutableState +import org.holoeasy.util.AABB + +class TextLine( + plugin: Plugin, + obj: String, + override val args: Array<*>? = null, + override val clickable: Boolean = false +) : ITextLine { + + private val line: Line = Line(plugin, EntityType.ARMOR_STAND) + override var obj: String = "" + var clickEvent : ClickEvent? = null + private var firstRender = true + + init { + if (args == null) { + this.obj = obj + } else { + this.obj = obj.replace("{}", "%s") + } + } + + var hitbox: AABB? = null + private set + private var isEmpty = false + + override val textLine: TextLine + get() = this + + override fun parse(player: Player): String { + if (args == null) { + return obj + } + val res = arrayOfNulls(args.size) + for (i in args.indices) { + val tmp = args[i] + if(tmp is MutableState<*>) { + res[i] = tmp.get() + if(firstRender) { + firstRender = false + tmp.addObserver(pvt) + } + } else { + res[i] = tmp + } + } + + return String.format(obj, args = res) + } + + override fun onClick(clickEvent: ClickEvent) { + this.clickEvent = clickEvent + } + + override val plugin: Plugin + get() = line.plugin + override val type: ILine.Type + get() = ILine.Type.TEXT_LINE + override val entityId: Int + get() = line.entityID + override val location: Location? + get() = line.location + + + override var pvt = ILine.PrivateConfig(this) + + override fun setLocation(value: Location) { + line.location = value + if (clickable) { + val chars = obj.length.toDouble() + val size = 0.105 + val dist = size * (chars / 2.0) + + hitbox = AABB( + AABB.Vec3D(-dist, -0.040, -dist), + AABB.Vec3D(dist, +0.040, dist) + ).also { + it.translate(AABB.Vec3D.fromLocation(value.clone().add(0.0, 2.35, 0.0))) + } + } + } + + override fun hide(player: Player) { + line.destroy(player) + } + + override fun teleport(player: Player) { + line.teleport(player) + } + + override fun show(player: Player) { + isEmpty = obj.isEmpty() + if (!isEmpty) { + line.spawn(player) + val packet = IPacket.get(IPacket.Type.METADATA_TEXT) + .metadata(entityId, parse(player)) + packet.send(player) + } + } + + override fun update(player: Player) { + val spawnBefore = ((if (isEmpty) 1 else 0) or ((if (obj.isEmpty()) 1 else 0) shl 1)) + /* 0x00 = is already showed + 0x01 = is hided but now has changed + 0x02 = is already showed but is empty + 0x03 = is hided and isn't changed */ + when (spawnBefore) { + 0x03 -> {} + 0x02 -> { + line.destroy(player) + isEmpty = true + } + + 0x01 -> { + line.spawn(player) + isEmpty = false + IPacket.get(IPacket.Type.METADATA_TEXT) + .metadata(entityId, parse(player)).send(player) + } + + 0x00 -> + IPacket.get(IPacket.Type.METADATA_TEXT) + .metadata(entityId, parse(player), invisible = false) + .send(player) + } + } + + +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/holoeasy/packet/IPacket.kt b/core/src/main/kotlin/org/holoeasy/packet/IPacket.kt new file mode 100644 index 0000000..0ac58fc --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/packet/IPacket.kt @@ -0,0 +1,104 @@ +package org.holoeasy.packet + + +import org.holoeasy.HoloEasy +import org.holoeasy.packet.delete.DeletePacketA +import org.holoeasy.packet.delete.DeletePacketB +import org.holoeasy.packet.delete.IDeletePacket +import org.holoeasy.packet.metadata.item.* +import org.holoeasy.packet.metadata.text.* +import org.holoeasy.packet.spawn.* +import org.holoeasy.packet.teleport.ITeleportPacket +import org.holoeasy.packet.teleport.TeleportPacketA +import org.holoeasy.packet.teleport.TeleportPacketB +import org.holoeasy.packet.velocity.IVelocityPacket +import org.holoeasy.packet.velocity.VelocityPacketA +import org.holoeasy.util.VersionEnum +import org.holoeasy.util.VersionUtil +import kotlin.reflect.KClass + +interface IPacket { + + val versionSupport: Array> + + private fun isCurrentVersion(): Boolean { + for (range in versionSupport) { + if (VersionUtil.CLEAN_VERSION in range) { + return true + } + } + return false + } + + companion object { + + private val cache = mutableMapOf, IPacket>() + + @JvmStatic + fun get(type: Type): T { + val cached = cache[type] + if (cached != null) { + return cached as T + } + + val rightImpl = type.impls.firstOrNull(IPacket::isCurrentVersion) + if (rightImpl != null) { + cache[type] = rightImpl + return rightImpl as T + } + + if (HoloEasy.useLastSupportedVersion) { + return type.impls.last() as T + } + + throw RuntimeException( + """ + No version support for ${type.abs.simpleName} packet + Set HologramLib.useLastSupportedVersion to true or + open an issue at https://github.com/unldenis/Hologram-Lib + """.trimIndent() + ) + } + + } + + class Type(internal val abs: KClass, vararg val impls: IPacket) { + + companion object { + @JvmField + val DELETE = Type(IDeletePacket::class, DeletePacketA, DeletePacketB) + + @JvmField + val METADATA_TEXT = Type( + IMetadataTextPacket::class, + MetadataTextPacketA, + MetadataTextPacketB, + MetadataTextPacketC, + MetadataTextPacketD, + MetadataTextPacketE + ) + + @JvmField + val METADATA_ITEM = Type( + IMetadataItemPacket::class, + MetadataItemPacketA, + MetadataItemPacketB, + MetadataItemPacketC, + MetadataItemPacketD, + MetadataItemPacketE + ) + + @JvmField + val SPAWN = Type(ISpawnPacket::class, SpawnPacketA, SpawnPacketB, SpawnPacketC, SpawnPacketD) + + @JvmField + val TELEPORT = Type(ITeleportPacket::class, TeleportPacketA, TeleportPacketB) + + @JvmField + val VELOCITY = Type(IVelocityPacket::class, VelocityPacketA) + } + + } + + +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/holoeasy/packet/PacketBuilder.kt b/core/src/main/kotlin/org/holoeasy/packet/PacketBuilder.kt new file mode 100644 index 0000000..6025e6f --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/packet/PacketBuilder.kt @@ -0,0 +1,9 @@ +package org.holoeasy.packet + +import com.comphenix.protocol.PacketType +import com.comphenix.protocol.events.PacketContainer + + +fun packet(type: PacketType, initializer: PacketContainer.() -> Unit): PacketContainer { + return PacketContainer(type).apply(initializer) +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/holoeasy/packet/delete/DeletePacketA.kt b/core/src/main/kotlin/org/holoeasy/packet/delete/DeletePacketA.kt new file mode 100644 index 0000000..147975f --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/packet/delete/DeletePacketA.kt @@ -0,0 +1,20 @@ +package org.holoeasy.packet.delete + +import com.comphenix.protocol.PacketType +import com.comphenix.protocol.events.PacketContainer +import org.holoeasy.ext.set +import org.holoeasy.packet.packet +import org.holoeasy.util.VersionEnum + +object DeletePacketA : IDeletePacket { + override val versionSupport: Array> + get() = arrayOf(VersionEnum.V1_8..VersionEnum.V1_16) + + override fun delete(entityId: Int): PacketContainer { + return packet(PacketType.Play.Server.ENTITY_DESTROY) { + integerArrays[0] = intArrayOf(entityId) + } + } + + +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/holoeasy/packet/delete/DeletePacketB.kt b/core/src/main/kotlin/org/holoeasy/packet/delete/DeletePacketB.kt new file mode 100644 index 0000000..8acdcf2 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/packet/delete/DeletePacketB.kt @@ -0,0 +1,19 @@ +package org.holoeasy.packet.delete + +import com.comphenix.protocol.PacketType +import com.comphenix.protocol.events.PacketContainer +import org.holoeasy.ext.set +import org.holoeasy.packet.packet +import org.holoeasy.util.VersionEnum + +object DeletePacketB : IDeletePacket { + + override val versionSupport: Array> + get() = arrayOf(VersionEnum.V1_17..VersionEnum.V1_20) + + override fun delete(entityId: Int): PacketContainer { + return packet(PacketType.Play.Server.ENTITY_DESTROY) { + intLists[0] = listOf(entityId) + } + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/holoeasy/packet/delete/IDeletePacket.kt b/core/src/main/kotlin/org/holoeasy/packet/delete/IDeletePacket.kt new file mode 100644 index 0000000..d657b5f --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/packet/delete/IDeletePacket.kt @@ -0,0 +1,9 @@ +package org.holoeasy.packet.delete + +import com.comphenix.protocol.events.PacketContainer +import org.holoeasy.packet.IPacket + +interface IDeletePacket : IPacket { + + fun delete(entityId: Int): PacketContainer +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/holoeasy/packet/metadata/item/IMetadataItemPacket.kt b/core/src/main/kotlin/org/holoeasy/packet/metadata/item/IMetadataItemPacket.kt new file mode 100644 index 0000000..f47b735 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/packet/metadata/item/IMetadataItemPacket.kt @@ -0,0 +1,9 @@ +package org.holoeasy.packet.metadata.item + +import com.comphenix.protocol.events.PacketContainer +import org.bukkit.inventory.ItemStack +import org.holoeasy.packet.IPacket + +interface IMetadataItemPacket : IPacket { + fun metadata(entityId: Int, item: ItemStack): PacketContainer +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/holoeasy/packet/metadata/item/MetadataItemPacketA.kt b/core/src/main/kotlin/org/holoeasy/packet/metadata/item/MetadataItemPacketA.kt new file mode 100644 index 0000000..103706b --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/packet/metadata/item/MetadataItemPacketA.kt @@ -0,0 +1,34 @@ +package org.holoeasy.packet.metadata.item + +import com.comphenix.protocol.PacketType +import com.comphenix.protocol.events.PacketContainer +import com.comphenix.protocol.wrappers.WrappedDataWatcher +import org.bukkit.inventory.ItemStack +import org.holoeasy.ext.bukkitGeneric +import org.holoeasy.ext.set +import org.holoeasy.ext.setByte +import org.holoeasy.packet.packet +import org.holoeasy.util.VersionEnum + + +object MetadataItemPacketA : IMetadataItemPacket { + override val versionSupport: Array> + get() = arrayOf(VersionEnum.V1_8..VersionEnum.V1_8) + + override fun metadata(entityId: Int, item: ItemStack): PacketContainer { + val watcher = WrappedDataWatcher() + + +// watcher.setObject(5, true) +// watcher.setObject(15, 1.toByte()) + watcher.setObject(10, item.bukkitGeneric()) + + + return packet(PacketType.Play.Server.ENTITY_METADATA) { + integers[0] = entityId + watchableCollectionModifier[0] = watcher.watchableObjects + } + } + + +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/holoeasy/packet/metadata/item/MetadataItemPacketB.kt b/core/src/main/kotlin/org/holoeasy/packet/metadata/item/MetadataItemPacketB.kt new file mode 100644 index 0000000..7906c60 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/packet/metadata/item/MetadataItemPacketB.kt @@ -0,0 +1,31 @@ +package org.holoeasy.packet.metadata.item + +import com.comphenix.protocol.PacketType +import com.comphenix.protocol.events.PacketContainer +import com.comphenix.protocol.wrappers.WrappedDataWatcher +import org.bukkit.inventory.ItemStack +import org.holoeasy.ext.set +import org.holoeasy.ext.setBool +import org.holoeasy.ext.setItemStack +import org.holoeasy.packet.packet +import org.holoeasy.util.VersionEnum + + +object MetadataItemPacketB : IMetadataItemPacket { + override val versionSupport: Array> + get() = arrayOf(VersionEnum.V1_9..VersionEnum.V1_12) + + override fun metadata(entityId: Int, item: ItemStack): PacketContainer { + val watcher = WrappedDataWatcher() + + watcher.setBool(5, true) + watcher.setItemStack(6, item) + + return packet(PacketType.Play.Server.ENTITY_METADATA) { + integers[0] = entityId + watchableCollectionModifier[0] = watcher.watchableObjects + } + } + + +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/holoeasy/packet/metadata/item/MetadataItemPacketC.kt b/core/src/main/kotlin/org/holoeasy/packet/metadata/item/MetadataItemPacketC.kt new file mode 100644 index 0000000..1910dc6 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/packet/metadata/item/MetadataItemPacketC.kt @@ -0,0 +1,32 @@ +package org.holoeasy.packet.metadata.item + +import com.comphenix.protocol.PacketType +import com.comphenix.protocol.events.PacketContainer +import com.comphenix.protocol.wrappers.EnumWrappers.EntityPose +import com.comphenix.protocol.wrappers.WrappedDataWatcher +import org.bukkit.inventory.ItemStack +import org.holoeasy.ext.set +import org.holoeasy.ext.setBool +import org.holoeasy.ext.setItemStack +import org.holoeasy.packet.packet +import org.holoeasy.util.VersionEnum + + +object MetadataItemPacketC : IMetadataItemPacket { + override val versionSupport: Array> + get() = arrayOf(VersionEnum.V1_13..VersionEnum.V1_18) + + override fun metadata(entityId: Int, item: ItemStack): PacketContainer { + val watcher = WrappedDataWatcher() + + watcher.setBool(5, true) + watcher.setItemStack(7, item) + + return packet(PacketType.Play.Server.ENTITY_METADATA) { + integers[0] = entityId + watchableCollectionModifier[0] = watcher.watchableObjects + } + } + + +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/holoeasy/packet/metadata/item/MetadataItemPacketD.kt b/core/src/main/kotlin/org/holoeasy/packet/metadata/item/MetadataItemPacketD.kt new file mode 100644 index 0000000..60093ad --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/packet/metadata/item/MetadataItemPacketD.kt @@ -0,0 +1,47 @@ +package org.holoeasy.packet.metadata.item + +import com.comphenix.protocol.PacketType +import com.comphenix.protocol.events.PacketContainer +import com.comphenix.protocol.utility.MinecraftVersion +import com.comphenix.protocol.wrappers.WrappedChatComponent +import com.comphenix.protocol.wrappers.WrappedDataValue +import com.comphenix.protocol.wrappers.WrappedDataWatcher +import com.comphenix.protocol.wrappers.WrappedDataWatcher.WrappedDataWatcherObject +import com.google.common.collect.Lists +import org.bukkit.inventory.ItemStack +import org.holoeasy.ext.bukkitGeneric +import org.holoeasy.ext.parse119 +import org.holoeasy.ext.setBool +import org.holoeasy.util.BOOL_SERIALIZER +import org.holoeasy.util.ITEM_SERIALIZER +import org.holoeasy.util.VersionEnum +import java.util.* + + +object MetadataItemPacketD : IMetadataItemPacket { + override val versionSupport: Array> + get() = arrayOf(VersionEnum.V1_19..VersionEnum.V1_19) + + override fun metadata(entityId: Int, item: ItemStack): PacketContainer { + val packet = PacketContainer(PacketType.Play.Server.ENTITY_METADATA) + packet.integers.write(0, entityId) + + val watcher = WrappedDataWatcher() + + val gravity = WrappedDataWatcherObject( + 5, BOOL_SERIALIZER + ) + watcher.setObject(gravity,true) + + val itemSer = WrappedDataWatcherObject( + 8, ITEM_SERIALIZER + ) + watcher.setObject(itemSer, item.bukkitGeneric()) + + // https://www.spigotmc.org/threads/unable-to-modify-entity-metadata-packet-using-protocollib-1-19-3.582442/#post-4517187 + packet.parse119(watcher) + + return packet + } + +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/holoeasy/packet/metadata/item/MetadataItemPacketE.kt b/core/src/main/kotlin/org/holoeasy/packet/metadata/item/MetadataItemPacketE.kt new file mode 100644 index 0000000..1f9a9e8 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/packet/metadata/item/MetadataItemPacketE.kt @@ -0,0 +1,36 @@ +package org.holoeasy.packet.metadata.item + +import com.comphenix.protocol.PacketType +import com.comphenix.protocol.events.PacketContainer +import com.comphenix.protocol.wrappers.WrappedDataValue +import org.bukkit.inventory.ItemStack +import org.holoeasy.ext.bukkitGeneric +import org.holoeasy.util.BOOL_SERIALIZER +import org.holoeasy.util.ITEM_SERIALIZER +import org.holoeasy.util.VersionEnum + + +object MetadataItemPacketE : IMetadataItemPacket { + override val versionSupport: Array> + get() = arrayOf(VersionEnum.V1_19..VersionEnum.V1_20) + + override fun metadata(entityId: Int, item: ItemStack): PacketContainer { + val packet = PacketContainer(PacketType.Play.Server.ENTITY_METADATA) + + packet.integers.write(0, entityId) + + packet.dataValueCollectionModifier + .write( + 0, listOf( + WrappedDataValue(5, BOOL_SERIALIZER, true), + + WrappedDataValue(8, ITEM_SERIALIZER, item.bukkitGeneric()) + ) + ) + + + return packet + } + + +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/holoeasy/packet/metadata/text/IMetadataTextPacket.kt b/core/src/main/kotlin/org/holoeasy/packet/metadata/text/IMetadataTextPacket.kt new file mode 100644 index 0000000..c8562c5 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/packet/metadata/text/IMetadataTextPacket.kt @@ -0,0 +1,9 @@ +package org.holoeasy.packet.metadata.text + +import com.comphenix.protocol.events.PacketContainer +import org.holoeasy.packet.IPacket + +interface IMetadataTextPacket : IPacket { + + fun metadata(entityId: Int, nameTag: String?, invisible : Boolean = true): PacketContainer +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/holoeasy/packet/metadata/text/MetadataTextPacketA.kt b/core/src/main/kotlin/org/holoeasy/packet/metadata/text/MetadataTextPacketA.kt new file mode 100644 index 0000000..cecd647 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/packet/metadata/text/MetadataTextPacketA.kt @@ -0,0 +1,33 @@ +package org.holoeasy.packet.metadata.text + +import com.comphenix.protocol.PacketType +import com.comphenix.protocol.events.PacketContainer +import com.comphenix.protocol.wrappers.WrappedDataWatcher +import org.holoeasy.ext.set +import org.holoeasy.packet.packet +import org.holoeasy.util.VersionEnum + +object MetadataTextPacketA : IMetadataTextPacket { + override val versionSupport: Array> + get() = arrayOf(VersionEnum.V1_8..VersionEnum.V1_8) + + override fun metadata(entityId: Int, nameTag: String?, invisible: Boolean): PacketContainer { + val watcher = WrappedDataWatcher() + + if (invisible) + watcher.setObject(0, 0x20.toByte()) + + if(nameTag != null) { + watcher.setObject(2, nameTag) + watcher.setObject(3, 1.toByte()) + } + + + return packet(PacketType.Play.Server.ENTITY_METADATA) { + integers[0] = entityId + watchableCollectionModifier[0] = watcher.watchableObjects + } + } + + +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/holoeasy/packet/metadata/text/MetadataTextPacketB.kt b/core/src/main/kotlin/org/holoeasy/packet/metadata/text/MetadataTextPacketB.kt new file mode 100644 index 0000000..42114e9 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/packet/metadata/text/MetadataTextPacketB.kt @@ -0,0 +1,36 @@ +package org.holoeasy.packet.metadata.text + +import com.comphenix.protocol.PacketType +import com.comphenix.protocol.events.PacketContainer +import com.comphenix.protocol.wrappers.WrappedDataWatcher +import org.holoeasy.ext.set +import org.holoeasy.ext.setBool +import org.holoeasy.ext.setByte +import org.holoeasy.ext.setString +import org.holoeasy.packet.packet +import org.holoeasy.util.VersionEnum + +object MetadataTextPacketB : IMetadataTextPacket { + override val versionSupport: Array> + get() = arrayOf(VersionEnum.V1_9..VersionEnum.V1_12) + + override fun metadata(entityId: Int, nameTag: String?, invisible : Boolean): PacketContainer { + val watcher = WrappedDataWatcher() + + if(invisible) + watcher.setByte(0, 0x20.toByte()) + + if(nameTag != null) { + watcher.setString(2, nameTag) + watcher.setBool(3, true) + } + + return packet(PacketType.Play.Server.ENTITY_METADATA) { + modifier.writeDefaults() + integers[0] = entityId + watchableCollectionModifier[0] = watcher.watchableObjects + } + } + + +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/holoeasy/packet/metadata/text/MetadataTextPacketC.kt b/core/src/main/kotlin/org/holoeasy/packet/metadata/text/MetadataTextPacketC.kt new file mode 100644 index 0000000..c3601d0 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/packet/metadata/text/MetadataTextPacketC.kt @@ -0,0 +1,34 @@ +package org.holoeasy.packet.metadata.text + +import com.comphenix.protocol.PacketType +import com.comphenix.protocol.events.PacketContainer +import com.comphenix.protocol.wrappers.WrappedDataWatcher +import org.holoeasy.ext.set +import org.holoeasy.ext.setBool +import org.holoeasy.ext.setByte +import org.holoeasy.ext.setChatComponent +import org.holoeasy.packet.packet +import org.holoeasy.util.VersionEnum + +object MetadataTextPacketC : IMetadataTextPacket { + override val versionSupport: Array> + get() = arrayOf(VersionEnum.V1_13..VersionEnum.V1_18) + + override fun metadata(entityId: Int, nameTag: String?, invisible : Boolean): PacketContainer { + val watcher = WrappedDataWatcher() + + if(invisible) + watcher.setByte(0, 0x20.toByte()) + + if(nameTag != null) { + watcher.setChatComponent(2, nameTag) + watcher.setBool(3, true) + } + return packet(PacketType.Play.Server.ENTITY_METADATA) { + integers[0] = entityId + watchableCollectionModifier[0] = watcher.watchableObjects + } + } + + +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/holoeasy/packet/metadata/text/MetadataTextPacketD.kt b/core/src/main/kotlin/org/holoeasy/packet/metadata/text/MetadataTextPacketD.kt new file mode 100644 index 0000000..d447c72 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/packet/metadata/text/MetadataTextPacketD.kt @@ -0,0 +1,35 @@ +package org.holoeasy.packet.metadata.text + +import com.comphenix.protocol.PacketType +import com.comphenix.protocol.events.PacketContainer +import com.comphenix.protocol.wrappers.WrappedDataWatcher +import org.holoeasy.ext.* +import org.holoeasy.packet.packet +import org.holoeasy.util.VersionEnum + +object MetadataTextPacketD : IMetadataTextPacket { + override val versionSupport: Array> + get() = arrayOf(VersionEnum.V1_19..VersionEnum.V1_19) + + override fun metadata(entityId: Int, nameTag: String?, invisible : Boolean): PacketContainer { + val packet = PacketContainer(PacketType.Play.Server.ENTITY_METADATA) + packet.integers.write(0, entityId) + + val watcher = WrappedDataWatcher() + + if(invisible) + watcher.setByte(0, 0x20.toByte()) + + if(nameTag != null) { + watcher.setChatComponent(2, nameTag) + watcher.setBool(3, true) + } + + // https://www.spigotmc.org/threads/unable-to-modify-entity-metadata-packet-using-protocollib-1-19-3.582442/#post-4517187 + packet.parse119(watcher) + + return packet + } + + +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/holoeasy/packet/metadata/text/MetadataTextPacketE.kt b/core/src/main/kotlin/org/holoeasy/packet/metadata/text/MetadataTextPacketE.kt new file mode 100644 index 0000000..8938480 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/packet/metadata/text/MetadataTextPacketE.kt @@ -0,0 +1,61 @@ +package org.holoeasy.packet.metadata.text + +import com.comphenix.protocol.PacketType +import com.comphenix.protocol.events.PacketContainer +import com.comphenix.protocol.wrappers.WrappedChatComponent +import com.comphenix.protocol.wrappers.WrappedDataValue +import com.comphenix.protocol.wrappers.WrappedDataWatcher +import org.holoeasy.util.BOOL_SERIALIZER +import org.holoeasy.util.BYTE_SERIALIZER +import org.holoeasy.util.VersionEnum +import java.util.* + +object MetadataTextPacketE : IMetadataTextPacket { + override val versionSupport: Array> + get() = arrayOf(VersionEnum.V1_20..VersionEnum.V1_20) + + override fun metadata(entityId: Int, nameTag: String?, invisible : Boolean): PacketContainer { + val packet = PacketContainer(PacketType.Play.Server.ENTITY_METADATA) + packet.integers.write(0, entityId) + + val watcher = WrappedDataWatcher() + + packet.watchableCollectionModifier.write(0, watcher.watchableObjects) + val wrappedDataValueList: MutableList = ArrayList() + + + + if(invisible) { + wrappedDataValueList.add( + WrappedDataValue(0, BYTE_SERIALIZER, 0x20.toByte()) + ) + } + + + nameTag?.let { + val opt: Optional<*> = Optional.of( + WrappedChatComponent.fromChatMessage( + it + )[0].handle + ) + + wrappedDataValueList.add( + WrappedDataValue( + 2, WrappedDataWatcher.Registry.getChatComponentSerializer(true), + opt + ) + ) + + wrappedDataValueList.add( + WrappedDataValue(3, BOOL_SERIALIZER, true) + ) + } + + + packet.dataValueCollectionModifier.write(0, wrappedDataValueList) + + return packet + } + + +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/holoeasy/packet/spawn/ISpawnPacket.kt b/core/src/main/kotlin/org/holoeasy/packet/spawn/ISpawnPacket.kt new file mode 100644 index 0000000..d5ecf10 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/packet/spawn/ISpawnPacket.kt @@ -0,0 +1,15 @@ +package org.holoeasy.packet.spawn + +import com.comphenix.protocol.events.PacketContainer +import org.bukkit.Location +import org.bukkit.entity.EntityType +import org.bukkit.plugin.Plugin +import org.holoeasy.packet.IPacket + +interface ISpawnPacket : IPacket { + + fun spawn( + entityId: Int, entityType: EntityType, location: Location, + plugin: Plugin? = null + ): PacketContainer +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/holoeasy/packet/spawn/SpawnPacketA.kt b/core/src/main/kotlin/org/holoeasy/packet/spawn/SpawnPacketA.kt new file mode 100644 index 0000000..9c49ed4 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/packet/spawn/SpawnPacketA.kt @@ -0,0 +1,55 @@ +package org.holoeasy.packet.spawn + +import com.comphenix.protocol.PacketType +import com.comphenix.protocol.events.PacketContainer +import com.comphenix.protocol.wrappers.WrappedDataWatcher +import org.bukkit.Bukkit +import org.bukkit.Location +import org.bukkit.Material +import org.bukkit.entity.EntityType +import org.bukkit.inventory.ItemStack +import org.bukkit.plugin.Plugin +import org.holoeasy.ext.set +import org.holoeasy.packet.packet +import org.holoeasy.util.BukkitFuture +import org.holoeasy.util.VersionEnum +import org.holoeasy.util.VersionUtil +import java.util.concurrent.CompletableFuture + +object SpawnPacketA : ISpawnPacket { + + private fun loadDefaultWatcher(plugin: Plugin): CompletableFuture { + return BukkitFuture.runSync(plugin) { + val world = Bukkit.getWorlds()[0] + val entity = + world.spawnEntity(Location(world, 0.0, 256.0, 0.0), EntityType.ARMOR_STAND) + entity.remove() + } + } + + private var defaultDataWatcher: WrappedDataWatcher? = null + + + override val versionSupport: Array> + get() = arrayOf(VersionEnum.V1_8..VersionEnum.V1_8) + + override fun spawn(entityId: Int, entityType: EntityType, location: Location, plugin: Plugin?): PacketContainer { + return packet(PacketType.Play.Server.SPAWN_ENTITY_LIVING) { + integers[0] = entityId + + integers[1] = VersionUtil.CLEAN_VERSION.armorstandId + + integers[2] = (location.x * 32).toInt() + integers[3] = (location.y * 32).toInt() + integers[4] = (location.z * 32).toInt() + + if (defaultDataWatcher == null) { + loadDefaultWatcher(plugin ?: throw RuntimeException("Plugin cannot be null")).join() + } + + dataWatcherModifier[0] = defaultDataWatcher + } + } + + +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/holoeasy/packet/spawn/SpawnPacketB.kt b/core/src/main/kotlin/org/holoeasy/packet/spawn/SpawnPacketB.kt new file mode 100644 index 0000000..9538982 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/packet/spawn/SpawnPacketB.kt @@ -0,0 +1,39 @@ +package org.holoeasy.packet.spawn + +import com.comphenix.protocol.PacketType +import com.comphenix.protocol.events.PacketContainer +import org.bukkit.Location +import org.bukkit.entity.EntityType +import org.bukkit.plugin.Plugin +import org.holoeasy.ext.set +import org.holoeasy.packet.packet +import org.holoeasy.util.VersionEnum +import org.holoeasy.util.VersionUtil +import java.util.* + +object SpawnPacketB : ISpawnPacket { + + override val versionSupport: Array> + get() = arrayOf(VersionEnum.V1_9..VersionEnum.V1_15) + + override fun spawn(entityId: Int, entityType: EntityType, location: Location, plugin: Plugin?): PacketContainer { + val extraData = 1 + + return packet(PacketType.Play.Server.SPAWN_ENTITY_LIVING) { + modifier.writeDefaults() + + integers[0] = entityId + integers[1] = if(entityType == EntityType.ARMOR_STAND) + VersionUtil.CLEAN_VERSION.armorstandId else VersionUtil.CLEAN_VERSION.droppedItemId + integers[2] = extraData + + uuiDs[0] = UUID.randomUUID() + + doubles[0] = location.x + doubles[1] = location.y + doubles[2] = location.z + } + + } + +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/holoeasy/packet/spawn/SpawnPacketC.kt b/core/src/main/kotlin/org/holoeasy/packet/spawn/SpawnPacketC.kt new file mode 100644 index 0000000..9774b13 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/packet/spawn/SpawnPacketC.kt @@ -0,0 +1,73 @@ +package org.holoeasy.packet.spawn + +import com.comphenix.protocol.PacketType +import com.comphenix.protocol.events.PacketContainer +import org.bukkit.Location +import org.bukkit.entity.EntityType +import org.bukkit.plugin.Plugin +import org.holoeasy.ext.set +import org.holoeasy.packet.packet +import org.holoeasy.util.VersionEnum +import org.holoeasy.util.VersionUtil +import java.util.* +import kotlin.math.max +import kotlin.math.min + +object SpawnPacketC : ISpawnPacket { + + override val versionSupport: Array> + get() = arrayOf(VersionEnum.V1_16..VersionEnum.V1_18) + + override fun spawn(entityId: Int, entityType: EntityType, location: Location, plugin: Plugin?): PacketContainer { + val extraData = 1 + + if(entityType == EntityType.ARMOR_STAND) { + return packet(PacketType.Play.Server.SPAWN_ENTITY_LIVING) { + modifier.writeDefaults() + + integers[0] = entityId + integers[1] = VersionUtil.CLEAN_VERSION.armorstandId + integers[2] = extraData + + uuiDs[0] = UUID.randomUUID() + + doubles[0] = location.x + doubles[1] = location.y + doubles[2] = location.z + } + } else { + return packet(PacketType.Play.Server.SPAWN_ENTITY) { + modifier.writeDefaults() + + integers[0] = entityId + + entityTypeModifier[0] = EntityType.DROPPED_ITEM + + uuiDs[0] = UUID.randomUUID() + + doubles[0] = location.x + doubles[1] = location.y + doubles[2] = location.z + + integers[2] = convertVelocity(0.0) + integers[3] = convertVelocity(0.0) + integers[4] = convertVelocity(0.0) + } + } + + } + + private fun convertVelocity(velocity: Double): Int { + /* + Minecraft represents a velocity within 4 blocks per second, in any direction, + by using the entire Short range, meaning you can only move up to 4 blocks/second + on any given direction + */ + return (clamp(velocity, -3.9, 3.9) * 8000).toInt() + } + private fun clamp(targetNum: Double, min: Double, max: Double): Double { + // Makes sure a number is within a range + return max(min, min(targetNum, max)) + } + +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/holoeasy/packet/spawn/SpawnPacketD.kt b/core/src/main/kotlin/org/holoeasy/packet/spawn/SpawnPacketD.kt new file mode 100644 index 0000000..c8ab4a3 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/packet/spawn/SpawnPacketD.kt @@ -0,0 +1,33 @@ +package org.holoeasy.packet.spawn + +import com.comphenix.protocol.PacketType +import com.comphenix.protocol.events.PacketContainer +import org.bukkit.Location +import org.bukkit.entity.EntityType +import org.bukkit.plugin.Plugin +import org.holoeasy.ext.set +import org.holoeasy.packet.packet +import org.holoeasy.util.VersionEnum +import java.util.* + +object SpawnPacketD : ISpawnPacket { + + override val versionSupport: Array> + get() = arrayOf(VersionEnum.V1_19..VersionEnum.V1_20) + + override fun spawn(entityId: Int, entityType: EntityType, location: Location, plugin: Plugin?): PacketContainer { + return packet(PacketType.Play.Server.SPAWN_ENTITY) { + integers[0] = entityId + + entityTypeModifier[0] = entityType + + uuiDs[0] = UUID.randomUUID() + + doubles[0] = location.x + doubles[1] = location.y + doubles[2] = location.z + + } + } + +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/holoeasy/packet/teleport/ITeleportPacket.kt b/core/src/main/kotlin/org/holoeasy/packet/teleport/ITeleportPacket.kt new file mode 100644 index 0000000..d52a7e6 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/packet/teleport/ITeleportPacket.kt @@ -0,0 +1,9 @@ +package org.holoeasy.packet.teleport + +import com.comphenix.protocol.events.PacketContainer +import org.bukkit.Location +import org.holoeasy.packet.IPacket + +interface ITeleportPacket : IPacket { + fun teleport(entityId: Int, location: Location): PacketContainer +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/holoeasy/packet/teleport/TeleportPacketA.kt b/core/src/main/kotlin/org/holoeasy/packet/teleport/TeleportPacketA.kt new file mode 100644 index 0000000..fe54d83 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/packet/teleport/TeleportPacketA.kt @@ -0,0 +1,29 @@ +package org.holoeasy.packet.teleport + +import com.comphenix.protocol.PacketType +import com.comphenix.protocol.events.PacketContainer +import org.bukkit.Location +import org.holoeasy.ext.compressAngle +import org.holoeasy.ext.fixCoordinate +import org.holoeasy.ext.set +import org.holoeasy.packet.packet +import org.holoeasy.util.VersionEnum + +object TeleportPacketA : ITeleportPacket { + override val versionSupport: Array> + get() = arrayOf(VersionEnum.V1_8..VersionEnum.V1_8) + + override fun teleport(entityId: Int, location: Location): PacketContainer { + return packet(PacketType.Play.Server.ENTITY_TELEPORT) { + integers[0] = entityId + integers[1] = location.x.fixCoordinate + integers[2] = location.y.fixCoordinate + integers[3] = location.z.fixCoordinate + bytes[0] = location.yaw.toDouble().compressAngle + bytes[1] = location.pitch.toDouble().compressAngle + booleans[0] = false + } + } + + +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/holoeasy/packet/teleport/TeleportPacketB.kt b/core/src/main/kotlin/org/holoeasy/packet/teleport/TeleportPacketB.kt new file mode 100644 index 0000000..4a83051 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/packet/teleport/TeleportPacketB.kt @@ -0,0 +1,33 @@ +package org.holoeasy.packet.teleport + +import com.comphenix.protocol.PacketType +import com.comphenix.protocol.events.PacketContainer +import org.bukkit.Location +import org.holoeasy.ext.compressAngle +import org.holoeasy.ext.set +import org.holoeasy.packet.packet +import org.holoeasy.util.VersionEnum + +object TeleportPacketB : ITeleportPacket { + + override val versionSupport: Array> + get() = arrayOf(VersionEnum.V1_9..VersionEnum.V1_20) + + override fun teleport(entityId: Int, location: Location): PacketContainer { + val teleportPacket = packet(PacketType.Play.Server.ENTITY_TELEPORT) { + integers[0] = entityId + + doubles[0] = location.x + doubles[1] = location.y + doubles[2] = location.z + + bytes[0] = location.yaw.toDouble().compressAngle + bytes[1] = location.pitch.toDouble().compressAngle + + booleans[0] = false + } + + return teleportPacket + } + +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/holoeasy/packet/velocity/IVelocityPacket.kt b/core/src/main/kotlin/org/holoeasy/packet/velocity/IVelocityPacket.kt new file mode 100644 index 0000000..ba54d34 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/packet/velocity/IVelocityPacket.kt @@ -0,0 +1,9 @@ +package org.holoeasy.packet.velocity + +import com.comphenix.protocol.events.PacketContainer +import org.holoeasy.packet.IPacket + +interface IVelocityPacket : IPacket { + fun velocity(entityId: Int, x: Int, y : Int, z : Int): PacketContainer + +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/holoeasy/packet/velocity/VelocityPacketA.kt b/core/src/main/kotlin/org/holoeasy/packet/velocity/VelocityPacketA.kt new file mode 100644 index 0000000..06e7967 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/packet/velocity/VelocityPacketA.kt @@ -0,0 +1,24 @@ +package org.holoeasy.packet.velocity + +import com.comphenix.protocol.PacketType +import com.comphenix.protocol.events.PacketContainer +import org.holoeasy.ext.set +import org.holoeasy.packet.packet +import org.holoeasy.util.VersionEnum + +object VelocityPacketA : IVelocityPacket { + + override val versionSupport: Array> + get() = arrayOf(VersionEnum.V1_8..VersionEnum.V1_20) + + override fun velocity(entityId: Int, x: Int, y: Int, z: Int): PacketContainer { + return packet(PacketType.Play.Server.ENTITY_VELOCITY) { + integers[0] = entityId + integers[1] = x + integers[2] = y + integers[3] = z + } + } + + +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/holoeasy/pool/HologramPool.kt b/core/src/main/kotlin/org/holoeasy/pool/HologramPool.kt new file mode 100644 index 0000000..f1dd245 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/pool/HologramPool.kt @@ -0,0 +1,118 @@ +package org.holoeasy.pool + +import org.holoeasy.config.HologramKey +import com.google.common.collect.ImmutableList +import org.bukkit.Bukkit +import org.bukkit.Location +import org.bukkit.event.EventHandler +import org.bukkit.event.Listener +import org.bukkit.event.player.PlayerQuitEvent +import org.bukkit.event.player.PlayerRespawnEvent +import org.bukkit.plugin.Plugin +import org.holoeasy.hologram.Hologram +import java.util.concurrent.ConcurrentHashMap + +class KeyAlreadyExistsException(key: HologramKey) : IllegalStateException("Key '$key' already exists") +class NoValueForKeyException(key: String) : IllegalStateException("No value for key '$key'") + +class HologramPool(override val plugin: Plugin, private val spawnDistance: Double) : Listener, IHologramPool { + + val holograms: MutableMap = ConcurrentHashMap() + + init { + Bukkit.getPluginManager().registerEvents(this, plugin) + + hologramTick() + } + + override fun get(key: HologramKey): Hologram { + return holograms[key] ?: throw NoValueForKeyException(key.id) + } + + override fun get(keyId: String) : Hologram { + for((key, holo) in holograms) { + if(key.id == keyId) { + return holo + } + } + throw NoValueForKeyException(keyId) + } + + override fun takeCareOf(key: HologramKey, value: Hologram) { + if (holograms.containsKey(key)) { + throw KeyAlreadyExistsException(key) + } + holograms[key] = value + } + + /** + * Removes the given hologram by from the handled Holograms of this pool. + * + * @param hologram the hologram of the pool to remove. + * @return true if any elements were removed + */ + override fun remove(key: HologramKey): Hologram? { + // if removed + val removed = holograms.remove(key) + removed?.let { + for (player in it.seeingPlayers) { + it.hide(player) + } + return it + } + return null + } + + @EventHandler + fun handleRespawn(event: PlayerRespawnEvent) { + val player = event.player + holograms + .values + .filter { it.isShownFor(player) } + .forEach { it.hide(player) } + } + + @EventHandler + fun handleQuit(event: PlayerQuitEvent) { + val player = event.player + holograms + .values + .filter { it.isShownFor(player) } + .forEach { it.seeingPlayers.remove(player) } + } + + /** + * Starts the hologram tick. + */ + private fun hologramTick() { + Bukkit.getScheduler().runTaskTimerAsynchronously(this.plugin, Runnable { + for (player in ImmutableList.copyOf(Bukkit.getOnlinePlayers())) { + for (hologram in this.holograms.values) { + val holoLoc = hologram.location + val playerLoc: Location = player.location + val isShown = hologram.isShownFor(player) + + if (holoLoc.world != playerLoc.world) { + if (isShown) { + hologram.hide(player) + } + continue + } else if (!holoLoc.world + // todo: log + !!.isChunkLoaded(holoLoc.blockX shr 4, holoLoc.blockZ shr 4) && isShown + ) { + hologram.hide(player) + continue + } + val inRange = holoLoc.distanceSquared(playerLoc) <= this.spawnDistance + + if (!inRange && isShown) { + hologram.hide(player) + } else if (inRange && !isShown) { + hologram.show(player) + } + } + } + }, 20L, 2L) + } +} diff --git a/core/src/main/kotlin/org/holoeasy/pool/IHologramPool.kt b/core/src/main/kotlin/org/holoeasy/pool/IHologramPool.kt new file mode 100644 index 0000000..3fd26c4 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/pool/IHologramPool.kt @@ -0,0 +1,21 @@ +package org.holoeasy.pool + +import org.holoeasy.config.HologramKey +import org.bukkit.plugin.Plugin +import org.holoeasy.hologram.Hologram +import org.jetbrains.annotations.ApiStatus.Experimental + +interface IHologramPool { + + val plugin: Plugin + + fun get(key: HologramKey): Hologram + + @Experimental + fun get(keyId: String) : Hologram + + fun takeCareOf(key: HologramKey, value: Hologram) + + fun remove(key: HologramKey): Hologram? + +} diff --git a/core/src/main/kotlin/org/holoeasy/pool/InteractiveHologramPool.kt b/core/src/main/kotlin/org/holoeasy/pool/InteractiveHologramPool.kt new file mode 100644 index 0000000..ea7ec51 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/pool/InteractiveHologramPool.kt @@ -0,0 +1,94 @@ +package org.holoeasy.pool + +import org.holoeasy.config.HologramKey +import org.bukkit.Bukkit +import org.bukkit.event.EventHandler +import org.bukkit.event.Listener +import org.bukkit.event.block.Action +import org.bukkit.event.player.PlayerInteractEvent +import org.bukkit.plugin.Plugin +import org.holoeasy.hologram.Hologram +import org.holoeasy.line.ILine +import org.holoeasy.line.ITextLine +import org.holoeasy.util.AABB + +class InteractiveHologramPool(private val pool: HologramPool, minHitDistance: Float, maxHitDistance: Float) : Listener, + IHologramPool { + + override val plugin: Plugin + get() = pool.plugin + + override fun get(key: HologramKey): Hologram { + return pool.get(key) + } + + override fun get(keyId: String): Hologram { + return pool.get(keyId) + } + + override fun takeCareOf(key: HologramKey, value: Hologram) { + pool.takeCareOf(key, value) + } + + override fun remove(key: HologramKey): Hologram? { + return pool.remove(key) + } + + + val minHitDistance: Float + val maxHitDistance: Float + + init { + require(!(minHitDistance < 0)) { "minHitDistance must be positive" } + require(!(maxHitDistance > 120)) { "maxHitDistance cannot be greater than 120" } + this.minHitDistance = minHitDistance + this.maxHitDistance = maxHitDistance + + Bukkit.getPluginManager().registerEvents(this, plugin) + } + + + @EventHandler + fun handleInteract(e: PlayerInteractEvent) { + Bukkit.getScheduler().runTaskAsynchronously(plugin, Runnable { + val player = e.player + if (e.action != Action.LEFT_CLICK_AIR) { + return@Runnable + } + FST@ for (hologram in pool.holograms.values) { + if (!hologram.isShownFor(player)) { + continue + } + for (line in hologram.lines) { + when (line.type) { + ILine.Type.TEXT_LINE -> { + val iTextLine = line as ITextLine + if (!iTextLine.clickable) { + continue + } + + val tL = iTextLine.textLine + if (tL.hitbox == null) { + continue + } + + val intersects = tL.hitbox!!.intersectsRay( + AABB.Ray3D(player.eyeLocation), minHitDistance, maxHitDistance + ) + if (intersects == null) { + continue + } + + tL.clickEvent?.onClick(player) + break@FST + } + + ILine.Type.EXTERNAL -> {} + ILine.Type.CLICKABLE_TEXT_LINE -> {} + ILine.Type.BLOCK_LINE -> {} + } + } + } + }) + } +} diff --git a/core/src/main/kotlin/org/holoeasy/reactive/MutableState.kt b/core/src/main/kotlin/org/holoeasy/reactive/MutableState.kt new file mode 100644 index 0000000..8816d72 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/reactive/MutableState.kt @@ -0,0 +1,29 @@ +package org.holoeasy.reactive + +data class MutableState(private var value : T) { + + private val observers= mutableListOf() + + fun get() : T { + return value + } + + fun set(newValue : T) { + value = newValue + this.notifyObservers() + } + + fun addObserver(observer: Observer) { + observers.add(observer) + } + + fun removeObserver(observer: Observer) { + observers.remove(observer) + } + + private fun notifyObservers() { + for (observer in observers) { + observer.observerUpdate() + } + } +} diff --git a/core/src/main/kotlin/org/holoeasy/reactive/Observer.kt b/core/src/main/kotlin/org/holoeasy/reactive/Observer.kt new file mode 100644 index 0000000..162dfbb --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/reactive/Observer.kt @@ -0,0 +1,5 @@ +package org.holoeasy.reactive + +interface Observer { + fun observerUpdate() +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/holoeasy/util/AABB.kt b/core/src/main/kotlin/org/holoeasy/util/AABB.kt new file mode 100644 index 0000000..c8a21e2 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/util/AABB.kt @@ -0,0 +1,250 @@ +/* + * Hologram-Lib - Asynchronous, high-performance Minecraft Hologram + * library for 1.8-1.18 servers. + * Copyright (C) unldenis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.holoeasy.util + +import org.bukkit.Location +import org.bukkit.util.Vector +import java.util.* +import kotlin.math.sqrt + +/** + * Represents an axix-aligned bounding box. + * + * @author Kristian + * @author unldenis + */ +class AABB +/** + * Creates a new instance from a minimum point and a maximum point. + */(private var min: Vec3D, private var max: Vec3D) { + /** + * Create a new com.github.unldenis.hologram.util.AABB from a given block. + * + * @param block - the block. + */ + constructor(block: Location) : this(Vec3D.fromLocation(block), Vec3D.fromLocation(block).add(Vec3D.UNIT_MAX)) + + /** + * Translate this AABB to a given point. + * + * @param vec - the point. + */ + fun translate(vec: Vec3D) { + this.min = min.add(vec) + this.max = max.add(vec) + } + + /** + * Calculates intersection with the given ray between a certain distance interval. + * + * + * Ray-box intersection is using IEEE numerical properties to ensure the test is both robust and + * efficient, as described in: + *

+ * `Amy Williams, Steve Barrus, R. Keith Morley, and Peter Shirley: "An + * Efficient and Robust Ray-Box Intersection Algorithm" Journal of graphics tools, 10(1):49-54, + * 2005` + * + * @param ray incident ray + * @return intersection point on the bounding box (only the first is returned) or null if no + * intersection + */ + fun intersectsRay(ray: Ray3D, minDist: Float, maxDist: Float): Vec3D? { + val invDir = Vec3D(1f / ray.direction.x, 1f / ray.direction.y, 1f / ray.direction.z) + + val signDirX = invDir.x < 0 + val signDirY = invDir.y < 0 + val signDirZ = invDir.z < 0 + + var bbox = if (signDirX) max else min + var tmin = (bbox.x - ray.x) * invDir.x + bbox = if (signDirX) min else max + var tmax = (bbox.x - ray.x) * invDir.x + bbox = if (signDirY) max else min + val tymin = (bbox.y - ray.y) * invDir.y + bbox = if (signDirY) min else max + val tymax = (bbox.y - ray.y) * invDir.y + + if ((tmin > tymax) || (tymin > tmax)) { + return null + } + if (tymin > tmin) { + tmin = tymin + } + if (tymax < tmax) { + tmax = tymax + } + + bbox = if (signDirZ) max else min + val tzmin = (bbox.z - ray.z) * invDir.z + bbox = if (signDirZ) min else max + val tzmax = (bbox.z - ray.z) * invDir.z + + if ((tmin > tzmax) || (tzmin > tmax)) { + return null + } + if (tzmin > tmin) { + tmin = tzmin + } + if (tzmax < tmax) { + tmax = tzmax + } + if ((tmin < maxDist) && (tmax > minDist)) { + return ray.getPointAtDistance(tmin) + } + return null + } + + open class Vec3D { + /** + * X coordinate. + */ + val x: Double + + /** + * Y coordinate. + */ + val y: Double + + /** + * Z coordinate. + */ + val z: Double + + /** + * Creates a new vector with the given coordinates. + * + * @param x the x + * @param y the y + * @param z the z + */ + constructor(x: Double, y: Double, z: Double) { + this.x = x + this.y = y + this.z = z + } + + /** + * Creates a new vector with the coordinates of the given vector. + * + * @param v vector to copy. + */ + constructor(v: Vec3D) { + this.x = v.x + this.y = v.y + this.z = v.z + } + + /** + * Add vector v and returns result as new vector. + * + * @param v vector to add + * @return result as new vector + */ + fun add(v: Vec3D): Vec3D { + return Vec3D(x + v.x, y + v.y, z + v.z) + } + + /** + * Scales vector uniformly and returns result as new vector. + * + * @param s scale factor + * @return new vector + */ + fun scale(s: Double): Vec3D { + return Vec3D(x * s, y * s, z * s) + } + + /** + * Normalizes the vector so that its magnitude = 1. + * + * @return The normalized vector. + */ + fun normalize(): Vec3D { + val mag = sqrt(x * x + y * y + z * z) + + if (mag > 0) { + return scale(1.0 / mag) + } + return this + } + + override fun equals(obj: Any?): Boolean { + if (obj is Vec3D) { + val v = obj + return x == v.x && y == v.y && z == v.z + } + return false + } + + override fun hashCode(): Int { + return Objects.hash(x, y, z) + } + + override fun toString(): String { + return String.format("{x: %g, y: %g, z: %g}", x, y, z) + } + + companion object { + /** + * Point with the coordinate (1, 1, 1). + */ + val UNIT_MAX: Vec3D = Vec3D(1.0, 1.0, 1.0) + + /** + * Construct a vector from a Bukkit location. + * + * @param loc - the Bukkit location. + */ + @JvmStatic + fun fromLocation(loc: Location): Vec3D { + return Vec3D(loc.x, loc.y, loc.z) + } + + /** + * Construct a copy of our immutable vector from Bukkit's mutable vector. + * + * @param v - Bukkit vector. + * @return A copy of the given vector. + */ + fun fromVector(v: Vector): Vec3D { + return Vec3D(v.x, v.y, v.z) + } + } + } + + class Ray3D(origin: Vec3D, direction: Vec3D) : Vec3D(origin) { + val direction: Vec3D = direction.normalize() + + /** + * Construct a 3D ray from a location. + * + * @param loc - the Bukkit location. + */ + constructor(loc: Location) : this(fromLocation(loc), fromVector(loc.direction)) + + fun getPointAtDistance(dist: Double): Vec3D { + return add(direction.scale(dist)) + } + + override fun toString(): String { + return "origin: " + super.toString() + " dir: " + direction + } + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/holoeasy/util/BukkitFuture.kt b/core/src/main/kotlin/org/holoeasy/util/BukkitFuture.kt new file mode 100644 index 0000000..af0e233 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/util/BukkitFuture.kt @@ -0,0 +1,152 @@ +/* + * Hologram-Lib - Asynchronous, high-performance Minecraft Hologram + * library for 1.8-1.18 servers. + * Copyright (C) unldenis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.holoeasy.util + +import org.bukkit.Bukkit +import org.bukkit.plugin.Plugin +import java.util.concurrent.CompletableFuture +import java.util.function.BiConsumer +import java.util.function.Supplier + +object BukkitFuture { + /** + * Returns a new CompletableFuture that is asynchronously completed by Bukkit schedule with the + * value obtained by calling the given Supplier. + * + * @param supplier a function returning the value to be used to complete the returned + * CompletableFuture + * @param the function's return type + */ + fun supplyAsync( + plugin: Plugin, + supplier: Supplier + ): CompletableFuture { + val future = CompletableFuture() + Bukkit.getScheduler().runTaskAsynchronously(plugin, Runnable { + try { + future.complete(supplier.get()) + } catch (t: Throwable) { + future.completeExceptionally(t) + } + }) + return future + } + + /** + * Returns a new CompletableFuture that is asynchronously completed by Bukkit schedule after it + * runs the given action. + * + * @param runnable the action to run before completing the returned CompletableFuture + */ + fun runAsync( + plugin: Plugin, + runnable: Runnable + ): CompletableFuture { + val future = CompletableFuture() + Bukkit.getScheduler().runTaskAsynchronously(plugin, Runnable { + try { + runnable.run() + future.complete(null) + } catch (t: Throwable) { + future.completeExceptionally(t) + } + }) + return future + } + + /** + * Returns a new CompletableFuture that is synchronously completed by Bukkit schedule with the + * value obtained by calling the given Supplier. + * + * @param supplier a function returning the value to be used to complete the returned + * CompletableFuture + * @param the function's return type + */ + fun supplySync( + plugin: Plugin, + supplier: Supplier + ): CompletableFuture { + val future = CompletableFuture() + if (Bukkit.isPrimaryThread()) { + try { + future.complete(supplier.get()) + } catch (t: Throwable) { + future.completeExceptionally(t) + } + } else { + Bukkit.getScheduler().runTask(plugin, Runnable { + try { + future.complete(supplier.get()) + } catch (t: Throwable) { + future.completeExceptionally(t) + } + }) + } + return future + } + + /** + * Returns a new CompletableFuture that is synchronously completed by Bukkit schedule after it + * runs the given action. + * + * @param runnable the action to run before completing the returned CompletableFuture + */ + fun runSync( + plugin: Plugin, + runnable: Runnable + ): CompletableFuture { + val future = CompletableFuture() + if (Bukkit.isPrimaryThread()) { + try { + runnable.run() + future.complete(null) + } catch (t: Throwable) { + future.completeExceptionally(t) + } + } else { + Bukkit.getScheduler().runTask(plugin, Runnable { + try { + runnable.run() + future.complete(null) + } catch (t: Throwable) { + future.completeExceptionally(t) + } + }) + } + return future + } + + /** + * Helper method to avoid boilerplate in CompletableFuture#whenComplete . + * + * @param action the BiConsumer of the whenComplete method that will be executed in the runnable + * synchronously. + * @param the function's return type + */ + fun sync( + plugin: Plugin, + action: BiConsumer + ): BiConsumer { + return BiConsumer { t: T, throwable: Throwable? -> + runSync( + plugin + ) { action.accept(t, throwable) } + } + } +} diff --git a/core/src/main/kotlin/org/holoeasy/util/Serializers.kt b/core/src/main/kotlin/org/holoeasy/util/Serializers.kt new file mode 100644 index 0000000..d8a5484 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/util/Serializers.kt @@ -0,0 +1,16 @@ +package org.holoeasy.util + +import com.comphenix.protocol.wrappers.WrappedDataWatcher + +val BYTE_SERIALIZER : WrappedDataWatcher.Serializer + get() = WrappedDataWatcher.Registry.get(java.lang.Byte::class.java) + +val BOOL_SERIALIZER : WrappedDataWatcher.Serializer + get() = WrappedDataWatcher.Registry.get(java.lang.Boolean::class.java) + +val STRING_SERIALIZER : WrappedDataWatcher.Serializer + get() = WrappedDataWatcher.Registry.get(java.lang.String::class.java) + +val ITEM_SERIALIZER : WrappedDataWatcher.Serializer + get() = WrappedDataWatcher.Registry.getItemStackSerializer(false) + diff --git a/core/src/main/kotlin/org/holoeasy/util/VersionUtil.kt b/core/src/main/kotlin/org/holoeasy/util/VersionUtil.kt new file mode 100644 index 0000000..c8d4395 --- /dev/null +++ b/core/src/main/kotlin/org/holoeasy/util/VersionUtil.kt @@ -0,0 +1,127 @@ +/* + * Hologram-Lib - Asynchronous, high-performance Minecraft Hologram + * library for 1.8-1.18 servers. + * Copyright (C) unldenis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.holoeasy.util + +import org.bukkit.Bukkit +import java.util.* + +object VersionUtil { + private val VERSION: String + + val CLEAN_VERSION: VersionEnum + + init { + val bpName = Bukkit.getServer().javaClass.getPackage().name + VERSION = bpName.substring(bpName.lastIndexOf(".") + 1) + val clean = VERSION.substring(0, VERSION.length - 3) + CLEAN_VERSION = VersionEnum.valueOf(clean.uppercase(Locale.getDefault())) + } + + fun isCompatible(ve: VersionEnum): Boolean { + return VERSION.lowercase(Locale.getDefault()).contains(ve.toString().lowercase(Locale.getDefault())) + } + + fun isAbove(ve: VersionEnum): Boolean { + return CLEAN_VERSION.ordinal >= ve.ordinal + } + + fun isBelow(ve: VersionEnum): Boolean { + return CLEAN_VERSION.ordinal <= ve.ordinal + } + + fun isBetween(ve1: VersionEnum, ve2: VersionEnum): Boolean { + return isAbove(ve1) && isBelow(ve2) + } + +} + + +enum class VersionEnum(armorstandId: Int, droppedItemId : Int) : Comparable { + MOCKBUK, // MockBukkit Test + + V1_8(30, 1), + V1_9, + V1_10, + V1_11, + V1_12, + V1_13(1, 32), + V1_14(1, 34), + V1_15(1, 35), + V1_16(1, 37), + V1_17(1, 41), + V1_18, + V1_19(2, 55), + V1_20, + + // for non breaking in future + V1_21, + V1_22 + ; + + var armorstandId : Int + private set + get() { + if(field == -1) { + field = entries + .last { it.newNMS && it.ordinal < ordinal } + .armorstandId + } + return field + } + var droppedItemId : Int + private set + get() { + if(field == -1) { + field = entries + .last { it.newNMS && it.ordinal < ordinal } + .droppedItemId + } + return field + } + + private var newNMS : Boolean + + init { + this.armorstandId = armorstandId + this.droppedItemId = droppedItemId + this.newNMS = true + } + + constructor() : this(-1, -1) { + newNMS = false + } + + + // retrieved with https://www.spigotmc.org/threads/entity-id-fetcher-for-protocol-use.444784/ + // var versions: Array = arrayOf( + // "1.8.9", + // "1.9.4", + // "1.10.2", + // "1.11.2", + // "1.12.2", + // "1.13.2", + // "1.14.4", + // "1.15.2", + // "1.16.5", + // "1.17.1", + // "1.18.2", + // "1.19.4", + // "1.20.4" + // ) +} \ No newline at end of file