diff options
Diffstat (limited to 'src/main/kotlin/util')
-rw-r--r-- | src/main/kotlin/util/AprilFoolsUtil.kt | 10 | ||||
-rw-r--r-- | src/main/kotlin/util/FirmFormatters.kt | 14 | ||||
-rw-r--r-- | src/main/kotlin/util/LegacyFormattingCode.kt | 54 | ||||
-rw-r--r-- | src/main/kotlin/util/MC.kt | 5 | ||||
-rw-r--r-- | src/main/kotlin/util/SBData.kt | 116 | ||||
-rw-r--r-- | src/main/kotlin/util/SkyBlockIsland.kt | 1 | ||||
-rw-r--r-- | src/main/kotlin/util/SkyblockId.kt | 23 | ||||
-rw-r--r-- | src/main/kotlin/util/mc/NbtItemData.kt | 4 | ||||
-rw-r--r-- | src/main/kotlin/util/mc/TolerantRegistriesOps.kt | 29 | ||||
-rw-r--r-- | src/main/kotlin/util/skyblock/ItemType.kt | 27 | ||||
-rw-r--r-- | src/main/kotlin/util/skyblock/Rarity.kt | 38 | ||||
-rw-r--r-- | src/main/kotlin/util/skyblock/SkyBlockItems.kt | 1 | ||||
-rw-r--r-- | src/main/kotlin/util/textutil.kt | 9 |
13 files changed, 235 insertions, 96 deletions
diff --git a/src/main/kotlin/util/AprilFoolsUtil.kt b/src/main/kotlin/util/AprilFoolsUtil.kt new file mode 100644 index 0000000..a940fa1 --- /dev/null +++ b/src/main/kotlin/util/AprilFoolsUtil.kt @@ -0,0 +1,10 @@ +package moe.nea.firmament.util + +import java.time.LocalDateTime +import java.time.Month + +object AprilFoolsUtil { + val isAprilFoolsDay = LocalDateTime.now().let { + it.dayOfMonth == 1 && it.month == Month.APRIL + } +} diff --git a/src/main/kotlin/util/FirmFormatters.kt b/src/main/kotlin/util/FirmFormatters.kt index 92fb9e5..4b32c2a 100644 --- a/src/main/kotlin/util/FirmFormatters.kt +++ b/src/main/kotlin/util/FirmFormatters.kt @@ -15,21 +15,25 @@ import net.minecraft.text.Text object FirmFormatters { fun formatCommas(int: Int, segments: Int = 3): String = formatCommas(int.toLong(), segments) - fun formatCommas(long: Long, segments: Int = 3): String { + fun formatCommas(long: Long, segments: Int = 3, includeSign: Boolean = false): String { + if (long < 0 && long != Long.MIN_VALUE) { + return "-" + formatCommas(-long, segments, false) + } + val prefix = if (includeSign) "+" else "" val α = long / 1000 if (α != 0L) { - return formatCommas(α, segments) + "," + (long - α * 1000).toString().padStart(3, '0') + return prefix + formatCommas(α, segments) + "," + (long - α * 1000).toString().padStart(3, '0') } - return long.toString() + return prefix + long.toString() } fun formatCommas(float: Float, fractionalDigits: Int): String = formatCommas(float.toDouble(), fractionalDigits) - fun formatCommas(double: Double, fractionalDigits: Int): String { + fun formatCommas(double: Double, fractionalDigits: Int, includeSign: Boolean = false): String { val long = double.toLong() val δ = (double - long).absoluteValue val μ = pow(10, fractionalDigits) val digits = (μ * δ).toInt().toString().padStart(fractionalDigits, '0').trimEnd('0') - return formatCommas(long) + (if (digits.isEmpty()) "" else ".$digits") + return formatCommas(long, includeSign = includeSign) + (if (digits.isEmpty()) "" else ".$digits") } fun formatDistance(distance: Double): String { diff --git a/src/main/kotlin/util/LegacyFormattingCode.kt b/src/main/kotlin/util/LegacyFormattingCode.kt index 44bacfc..1a5d1dd 100644 --- a/src/main/kotlin/util/LegacyFormattingCode.kt +++ b/src/main/kotlin/util/LegacyFormattingCode.kt @@ -1,35 +1,37 @@ - - package moe.nea.firmament.util import net.minecraft.util.Formatting enum class LegacyFormattingCode(val label: String, val char: Char, val index: Int) { - BLACK("BLACK", '0', 0), - DARK_BLUE("DARK_BLUE", '1', 1), - DARK_GREEN("DARK_GREEN", '2', 2), - DARK_AQUA("DARK_AQUA", '3', 3), - DARK_RED("DARK_RED", '4', 4), - DARK_PURPLE("DARK_PURPLE", '5', 5), - GOLD("GOLD", '6', 6), - GRAY("GRAY", '7', 7), - DARK_GRAY("DARK_GRAY", '8', 8), - BLUE("BLUE", '9', 9), - GREEN("GREEN", 'a', 10), - AQUA("AQUA", 'b', 11), - RED("RED", 'c', 12), - LIGHT_PURPLE("LIGHT_PURPLE", 'd', 13), - YELLOW("YELLOW", 'e', 14), - WHITE("WHITE", 'f', 15), - OBFUSCATED("OBFUSCATED", 'k', -1), - BOLD("BOLD", 'l', -1), - STRIKETHROUGH("STRIKETHROUGH", 'm', -1), - UNDERLINE("UNDERLINE", 'n', -1), - ITALIC("ITALIC", 'o', -1), - RESET("RESET", 'r', -1); + BLACK("BLACK", '0', 0), + DARK_BLUE("DARK_BLUE", '1', 1), + DARK_GREEN("DARK_GREEN", '2', 2), + DARK_AQUA("DARK_AQUA", '3', 3), + DARK_RED("DARK_RED", '4', 4), + DARK_PURPLE("DARK_PURPLE", '5', 5), + GOLD("GOLD", '6', 6), + GRAY("GRAY", '7', 7), + DARK_GRAY("DARK_GRAY", '8', 8), + BLUE("BLUE", '9', 9), + GREEN("GREEN", 'a', 10), + AQUA("AQUA", 'b', 11), + RED("RED", 'c', 12), + LIGHT_PURPLE("LIGHT_PURPLE", 'd', 13), + YELLOW("YELLOW", 'e', 14), + WHITE("WHITE", 'f', 15), + OBFUSCATED("OBFUSCATED", 'k', -1), + BOLD("BOLD", 'l', -1), + STRIKETHROUGH("STRIKETHROUGH", 'm', -1), + UNDERLINE("UNDERLINE", 'n', -1), + ITALIC("ITALIC", 'o', -1), + RESET("RESET", 'r', -1); + + companion object { + val byCode = entries.associateBy { it.char } + } - val modern = Formatting.byCode(char)!! + val modern = Formatting.byCode(char)!! - val formattingCode = "§$char" + val formattingCode = "§$char" } diff --git a/src/main/kotlin/util/MC.kt b/src/main/kotlin/util/MC.kt index 294334a..215d2a8 100644 --- a/src/main/kotlin/util/MC.kt +++ b/src/main/kotlin/util/MC.kt @@ -64,6 +64,8 @@ object MC { } fun sendCommand(command: String) { + // TODO: add a queue to this and sendServerChat + ErrorUtil.softCheck("Server commands have an implied /", !command.startsWith("/")) player?.networkHandler?.sendCommand(command) } @@ -96,8 +98,9 @@ object MC { inline val camera: Entity? get() = instance.cameraEntity inline val guiAtlasManager get() = instance.guiAtlasManager inline val world: ClientWorld? get() = TestUtil.unlessTesting { instance.world } + inline val playerName: String? get() = player?.name?.unformattedString inline var screen: Screen? - get() = TestUtil.unlessTesting{ instance.currentScreen } + get() = TestUtil.unlessTesting { instance.currentScreen } set(value) = instance.setScreen(value) val screenName get() = screen?.title?.unformattedString?.trim() inline val handledScreen: HandledScreen<*>? get() = instance.currentScreen as? HandledScreen<*> diff --git a/src/main/kotlin/util/SBData.kt b/src/main/kotlin/util/SBData.kt index 051d070..e785ff6 100644 --- a/src/main/kotlin/util/SBData.kt +++ b/src/main/kotlin/util/SBData.kt @@ -1,5 +1,6 @@ package moe.nea.firmament.util +import java.time.ZoneId import java.util.UUID import net.hypixel.modapi.HypixelModAPI import net.hypixel.modapi.packet.impl.clientbound.event.ClientboundLocationPacket @@ -10,63 +11,66 @@ import moe.nea.firmament.events.ProcessChatEvent import moe.nea.firmament.events.ProfileSwitchEvent import moe.nea.firmament.events.ServerConnectedEvent import moe.nea.firmament.events.SkyblockServerUpdateEvent -import moe.nea.firmament.events.WorldReadyEvent object SBData { - private val profileRegex = "Profile ID: ([a-z0-9\\-]+)".toRegex() - val profileSuggestTexts = listOf( - "CLICK THIS TO SUGGEST IT IN CHAT [DASHES]", - "CLICK THIS TO SUGGEST IT IN CHAT [NO DASHES]", - ) - var profileId: UUID? = null + private val profileRegex = "Profile ID: ([a-z0-9\\-]+)".toRegex() + val profileSuggestTexts = listOf( + "CLICK THIS TO SUGGEST IT IN CHAT [DASHES]", + "CLICK THIS TO SUGGEST IT IN CHAT [NO DASHES]", + ) + var profileId: UUID? = null - private var hasReceivedProfile = false - var locraw: Locraw? = null - val skyblockLocation: SkyBlockIsland? get() = locraw?.skyblockLocation - val hasValidLocraw get() = locraw?.server !in listOf("limbo", null) - val isOnSkyblock get() = locraw?.gametype == "SKYBLOCK" - var profileIdCommandDebounce = TimeMark.farPast() - fun init() { - ServerConnectedEvent.subscribe("SBData:onServerConnected") { - HypixelModAPI.getInstance().subscribeToEventPacket(ClientboundLocationPacket::class.java) - } - HypixelModAPI.getInstance().createHandler(ClientboundLocationPacket::class.java) { - MC.onMainThread { - val lastLocraw = locraw - locraw = Locraw(it.serverName, - it.serverType.getOrNull()?.name?.uppercase(), - it.mode.getOrNull(), - it.map.getOrNull()) - SkyblockServerUpdateEvent.publish(SkyblockServerUpdateEvent(lastLocraw, locraw)) - profileIdCommandDebounce = TimeMark.now() - } - } - SkyblockServerUpdateEvent.subscribe("SBData:sendProfileId") { - if (!hasReceivedProfile && isOnSkyblock && profileIdCommandDebounce.passedTime() > 10.seconds) { - profileIdCommandDebounce = TimeMark.now() - MC.sendServerCommand("profileid") - } - } - AllowChatEvent.subscribe("SBData:hideProfileSuggest") { event -> - if (event.unformattedString in profileSuggestTexts && profileIdCommandDebounce.passedTime() < 5.seconds) { - event.cancel() - } - } - ProcessChatEvent.subscribe(receivesCancelled = true, "SBData:loadProfile") { event -> - val profileMatch = profileRegex.matchEntire(event.unformattedString) - if (profileMatch != null) { - val oldProfile = profileId - try { - profileId = UUID.fromString(profileMatch.groupValues[1]) - hasReceivedProfile = true - } catch (e: IllegalArgumentException) { - profileId = null - e.printStackTrace() - } - if (oldProfile != profileId) { - ProfileSwitchEvent.publish(ProfileSwitchEvent(oldProfile, profileId)) - } - } - } - } + /** + * Source: https://hypixel-skyblock.fandom.com/wiki/Time_Systems + */ + val hypixelTimeZone = ZoneId.of("US/Eastern") + private var hasReceivedProfile = false + var locraw: Locraw? = null + val skyblockLocation: SkyBlockIsland? get() = locraw?.skyblockLocation + val hasValidLocraw get() = locraw?.server !in listOf("limbo", null) + val isOnSkyblock get() = locraw?.gametype == "SKYBLOCK" + var profileIdCommandDebounce = TimeMark.farPast() + fun init() { + ServerConnectedEvent.subscribe("SBData:onServerConnected") { + HypixelModAPI.getInstance().subscribeToEventPacket(ClientboundLocationPacket::class.java) + } + HypixelModAPI.getInstance().createHandler(ClientboundLocationPacket::class.java) { + MC.onMainThread { + val lastLocraw = locraw + locraw = Locraw(it.serverName, + it.serverType.getOrNull()?.name?.uppercase(), + it.mode.getOrNull(), + it.map.getOrNull()) + SkyblockServerUpdateEvent.publish(SkyblockServerUpdateEvent(lastLocraw, locraw)) + profileIdCommandDebounce = TimeMark.now() + } + } + SkyblockServerUpdateEvent.subscribe("SBData:sendProfileId") { + if (!hasReceivedProfile && isOnSkyblock && profileIdCommandDebounce.passedTime() > 10.seconds) { + profileIdCommandDebounce = TimeMark.now() + MC.sendServerCommand("profileid") + } + } + AllowChatEvent.subscribe("SBData:hideProfileSuggest") { event -> + if (event.unformattedString in profileSuggestTexts && profileIdCommandDebounce.passedTime() < 5.seconds) { + event.cancel() + } + } + ProcessChatEvent.subscribe(receivesCancelled = true, "SBData:loadProfile") { event -> + val profileMatch = profileRegex.matchEntire(event.unformattedString) + if (profileMatch != null) { + val oldProfile = profileId + try { + profileId = UUID.fromString(profileMatch.groupValues[1]) + hasReceivedProfile = true + } catch (e: IllegalArgumentException) { + profileId = null + e.printStackTrace() + } + if (oldProfile != profileId) { + ProfileSwitchEvent.publish(ProfileSwitchEvent(oldProfile, profileId)) + } + } + } + } } diff --git a/src/main/kotlin/util/SkyBlockIsland.kt b/src/main/kotlin/util/SkyBlockIsland.kt index c42a55c..f15cadb 100644 --- a/src/main/kotlin/util/SkyBlockIsland.kt +++ b/src/main/kotlin/util/SkyBlockIsland.kt @@ -35,6 +35,7 @@ private constructor( val PRIVATE_ISLAND = forMode("dynamic") val RIFT = forMode("rift") val MINESHAFT = forMode("mineshaft") + val GARDEN = forMode("garden") } val userFriendlyName diff --git a/src/main/kotlin/util/SkyblockId.kt b/src/main/kotlin/util/SkyblockId.kt index 1c1aa77..631b444 100644 --- a/src/main/kotlin/util/SkyblockId.kt +++ b/src/main/kotlin/util/SkyblockId.kt @@ -106,7 +106,10 @@ data class HypixelPetInfo( private val jsonparser = Json { ignoreUnknownKeys = true } -val ItemStack.extraAttributes: NbtCompound +var ItemStack.extraAttributes: NbtCompound + set(value) { + set(DataComponentTypes.CUSTOM_DATA, NbtComponent.of(value)) + } get() { val customData = get(DataComponentTypes.CUSTOM_DATA) ?: run { val component = NbtComponent.of(NbtCompound()) @@ -125,10 +128,26 @@ val ItemStack.skyblockUUID: UUID? private val petDataCache = WeakCache.memoize<ItemStack, Optional<HypixelPetInfo>>("PetInfo") { val jsonString = it.extraAttributes.getString("petInfo") if (jsonString.isNullOrBlank()) return@memoize Optional.empty() - ErrorUtil.catch<HypixelPetInfo?>("Could not decode hypixel pet info") { jsonparser.decodeFromString<HypixelPetInfo>(jsonString) } + ErrorUtil.catch<HypixelPetInfo?>("Could not decode hypixel pet info") { + jsonparser.decodeFromString<HypixelPetInfo>(jsonString) + } .or { null }.intoOptional() } +fun ItemStack.getUpgradeStars(): Int { + return extraAttributes.getInt("upgrade_level").takeIf { it > 0 } + ?: extraAttributes.getInt("dungeon_item_level").takeIf { it > 0 } + ?: 0 +} + +@Serializable +@JvmInline +value class ReforgeId(val id: String) + +fun ItemStack.getReforgeId(): ReforgeId? { + return extraAttributes.getString("modifier").takeIf { it.isNotBlank() }?.let(::ReforgeId) +} + val ItemStack.petData: HypixelPetInfo? get() = petDataCache(this).getOrNull() diff --git a/src/main/kotlin/util/mc/NbtItemData.kt b/src/main/kotlin/util/mc/NbtItemData.kt index e8a908f..0c49862 100644 --- a/src/main/kotlin/util/mc/NbtItemData.kt +++ b/src/main/kotlin/util/mc/NbtItemData.kt @@ -5,8 +5,8 @@ import net.minecraft.component.type.LoreComponent import net.minecraft.item.ItemStack import net.minecraft.text.Text -var ItemStack.loreAccordingToNbt - get() = get(DataComponentTypes.LORE)?.lines ?: listOf() +var ItemStack.loreAccordingToNbt: List<Text> + get() = get(DataComponentTypes.LORE)?.lines ?: listOf() set(value) { set(DataComponentTypes.LORE, LoreComponent(value)) } diff --git a/src/main/kotlin/util/mc/TolerantRegistriesOps.kt b/src/main/kotlin/util/mc/TolerantRegistriesOps.kt new file mode 100644 index 0000000..ce596a0 --- /dev/null +++ b/src/main/kotlin/util/mc/TolerantRegistriesOps.kt @@ -0,0 +1,29 @@ +package moe.nea.firmament.util.mc + +import com.mojang.serialization.DynamicOps +import java.util.Optional +import net.minecraft.registry.Registry +import net.minecraft.registry.RegistryKey +import net.minecraft.registry.RegistryOps +import net.minecraft.registry.RegistryWrapper +import net.minecraft.registry.entry.RegistryEntryOwner + +class TolerantRegistriesOps<T>( + delegate: DynamicOps<T>, + registryInfoGetter: RegistryInfoGetter +) : RegistryOps<T>(delegate, registryInfoGetter) { + constructor(delegate: DynamicOps<T>, registry: RegistryWrapper.WrapperLookup) : + this(delegate, CachedRegistryInfoGetter(registry)) + + class TolerantOwner<E> : RegistryEntryOwner<E> { + override fun ownerEquals(other: RegistryEntryOwner<E>?): Boolean { + return true + } + } + + override fun <E : Any?> getOwner(registryRef: RegistryKey<out Registry<out E>>?): Optional<RegistryEntryOwner<E>> { + return super.getOwner(registryRef).map { + TolerantOwner() + } + } +} diff --git a/src/main/kotlin/util/skyblock/ItemType.kt b/src/main/kotlin/util/skyblock/ItemType.kt index 6ddb077..7149379 100644 --- a/src/main/kotlin/util/skyblock/ItemType.kt +++ b/src/main/kotlin/util/skyblock/ItemType.kt @@ -13,6 +13,13 @@ value class ItemType private constructor(val name: String) { return ItemType(name) } + private val obfuscatedRegex = "§[kK].*?(§[0-9a-fA-FrR]|$)".toRegex() + fun fromEscapeCodeLore(lore: String): ItemType? { + return lore.replace(obfuscatedRegex, "").trim().substringAfter(" ", "") + .takeIf { it.isNotEmpty() } + ?.let(::ofName) + } + fun fromItemStack(itemStack: ItemStack): ItemType? { if (itemStack.petData != null) return PET @@ -26,13 +33,31 @@ value class ItemType private constructor(val name: String) { if (type.isEmpty()) return null return ofName(type) } - return null + return itemStack.loreAccordingToNbt.lastOrNull()?.directLiteralStringContent?.let(::fromEscapeCodeLore) } + // TODO: some of those are not actual in game item types, but rather ones included in the repository to splat to multiple in game types. codify those somehow + val SWORD = ofName("SWORD") val DRILL = ofName("DRILL") val PICKAXE = ofName("PICKAXE") val GAUNTLET = ofName("GAUNTLET") + val LONGSWORD = ofName("LONG SWORD") + val EQUIPMENT = ofName("EQUIPMENT") + val FISHING_WEAPON = ofName("FISHING WEAPON") + val CLOAK = ofName("CLOAK") + val BELT = ofName("BELT") + val NECKLACE = ofName("NECKLACE") + val BRACELET = ofName("BRACELET") + val GLOVES = ofName("GLOVES") + val ROD = ofName("ROD") + val FISHING_ROD = ofName("FISHING ROD") + val VACUUM = ofName("VACUUM") + val CHESTPLATE = ofName("CHESTPLATE") + val LEGGINGS = ofName("LEGGINGS") + val HELMET = ofName("HELMET") + val BOOTS = ofName("BOOTS") + val NIL = ofName("__NIL") /** * This one is not really official (it never shows up in game). diff --git a/src/main/kotlin/util/skyblock/Rarity.kt b/src/main/kotlin/util/skyblock/Rarity.kt index f26cefe..b19f371 100644 --- a/src/main/kotlin/util/skyblock/Rarity.kt +++ b/src/main/kotlin/util/skyblock/Rarity.kt @@ -1,7 +1,16 @@ package moe.nea.firmament.util.skyblock +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder import net.minecraft.item.ItemStack +import net.minecraft.text.Style import net.minecraft.text.Text +import net.minecraft.util.Formatting import moe.nea.firmament.util.StringUtil.words import moe.nea.firmament.util.collections.lastNotNullOfOrNull import moe.nea.firmament.util.mc.loreAccordingToNbt @@ -10,6 +19,7 @@ import moe.nea.firmament.util.unformattedString typealias RepoRarity = io.github.moulberry.repo.data.Rarity +@Serializable(with = Rarity.Serializer::class) enum class Rarity(vararg altNames: String) { COMMON, UNCOMMON, @@ -24,11 +34,37 @@ enum class Rarity(vararg altNames: String) { UNKNOWN ; - val names = setOf(name) + altNames + object Serializer : KSerializer<Rarity> { + override val descriptor: SerialDescriptor + get() = PrimitiveSerialDescriptor(Rarity::class.java.name, PrimitiveKind.STRING) + + override fun deserialize(decoder: Decoder): Rarity { + return valueOf(decoder.decodeString().replace(" ", "_")) + } + override fun serialize(encoder: Encoder, value: Rarity) { + encoder.encodeString(value.name) + } + } + + val names = setOf(name) + altNames + val text: Text get() = Text.literal(name).setStyle(Style.EMPTY.withColor(colourMap[this])) val neuRepoRarity: RepoRarity? = RepoRarity.entries.find { it.name == name } companion object { + // TODO: inline those formattings as fields + val colourMap = mapOf( + Rarity.COMMON to Formatting.WHITE, + Rarity.UNCOMMON to Formatting.GREEN, + Rarity.RARE to Formatting.BLUE, + Rarity.EPIC to Formatting.DARK_PURPLE, + Rarity.LEGENDARY to Formatting.GOLD, + Rarity.MYTHIC to Formatting.LIGHT_PURPLE, + Rarity.DIVINE to Formatting.AQUA, + Rarity.SPECIAL to Formatting.RED, + Rarity.VERY_SPECIAL to Formatting.RED, + Rarity.SUPREME to Formatting.DARK_RED, + ) val byName = entries.flatMap { en -> en.names.map { it to en } }.toMap() val fromNeuRepo = entries.associateBy { it.neuRepoRarity } diff --git a/src/main/kotlin/util/skyblock/SkyBlockItems.kt b/src/main/kotlin/util/skyblock/SkyBlockItems.kt index c94ebfe..cfd8429 100644 --- a/src/main/kotlin/util/skyblock/SkyBlockItems.kt +++ b/src/main/kotlin/util/skyblock/SkyBlockItems.kt @@ -7,4 +7,5 @@ object SkyBlockItems { val ENCHANTED_DIAMOND = SkyblockId("ENCHANTED_DIAMOND") val DIAMOND = SkyblockId("DIAMOND") val ANCESTRAL_SPADE = SkyblockId("ANCESTRAL_SPADE") + val REFORGE_ANVIL = SkyblockId("REFORGE_ANVIL") } diff --git a/src/main/kotlin/util/textutil.kt b/src/main/kotlin/util/textutil.kt index 5d95d7a..ab3de43 100644 --- a/src/main/kotlin/util/textutil.kt +++ b/src/main/kotlin/util/textutil.kt @@ -133,6 +133,7 @@ fun MutableText.darkGreen() = withColor(Formatting.DARK_GREEN) fun MutableText.purple() = withColor(Formatting.DARK_PURPLE) fun MutableText.pink() = withColor(Formatting.LIGHT_PURPLE) fun MutableText.yellow() = withColor(Formatting.YELLOW) +fun MutableText.gold() = withColor(Formatting.GOLD) fun MutableText.grey() = withColor(Formatting.GRAY) fun MutableText.red() = withColor(Formatting.RED) fun MutableText.white() = withColor(Formatting.WHITE) @@ -142,11 +143,15 @@ fun MutableText.bold(): MutableText = styled { it.withBold(true) } fun MutableText.clickCommand(command: String): MutableText { require(command.startsWith("/")) return this.styled { - it.withClickEvent(ClickEvent(ClickEvent.Action.RUN_COMMAND, - "/firm disablereiwarning")) + it.withClickEvent(ClickEvent(ClickEvent.Action.RUN_COMMAND, command)) } } +fun MutableText.prepend(text: Text): MutableText { + siblings.addFirst(text) + return this +} + fun Text.transformEachRecursively(function: (Text) -> Text): Text { val c = this.content if (c is TranslatableTextContent) { |