aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/kotlin/util')
-rw-r--r--src/main/kotlin/util/AprilFoolsUtil.kt10
-rw-r--r--src/main/kotlin/util/FirmFormatters.kt42
-rw-r--r--src/main/kotlin/util/LegacyFormattingCode.kt54
-rw-r--r--src/main/kotlin/util/MC.kt5
-rw-r--r--src/main/kotlin/util/MoulConfigUtils.kt13
-rw-r--r--src/main/kotlin/util/SBData.kt125
-rw-r--r--src/main/kotlin/util/ScoreboardUtil.kt72
-rw-r--r--src/main/kotlin/util/SkyBlockIsland.kt2
-rw-r--r--src/main/kotlin/util/SkyblockId.kt29
-rw-r--r--src/main/kotlin/util/mc/IntrospectableItemModelManager.kt7
-rw-r--r--src/main/kotlin/util/mc/NbtItemData.kt4
-rw-r--r--src/main/kotlin/util/mc/TolerantRegistriesOps.kt29
-rw-r--r--src/main/kotlin/util/render/TranslatedScissors.kt6
-rw-r--r--src/main/kotlin/util/skyblock/DungeonUtil.kt33
-rw-r--r--src/main/kotlin/util/skyblock/ItemType.kt31
-rw-r--r--src/main/kotlin/util/skyblock/Rarity.kt38
-rw-r--r--src/main/kotlin/util/skyblock/SkyBlockItems.kt1
-rw-r--r--src/main/kotlin/util/textutil.kt113
18 files changed, 427 insertions, 187 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..acb7102 100644
--- a/src/main/kotlin/util/FirmFormatters.kt
+++ b/src/main/kotlin/util/FirmFormatters.kt
@@ -9,27 +9,59 @@ import kotlin.io.path.isReadable
import kotlin.io.path.isRegularFile
import kotlin.io.path.listDirectoryEntries
import kotlin.math.absoluteValue
+import kotlin.math.roundToInt
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
import net.minecraft.text.Text
object FirmFormatters {
+
+ private inline fun shortIf(
+ value: Double, breakpoint: Double, char: String,
+ return_: (String) -> Nothing
+ ) {
+ if (value >= breakpoint) {
+ val broken = (value / breakpoint * 10).roundToInt()
+ if (broken > 99)
+ return_((broken / 10).toString() + char)
+ val decimals = broken.toString()
+ decimals.singleOrNull()?.let {
+ return_("0.$it$char")
+ }
+ return_("${decimals[0]}.${decimals[1]}$char")
+ }
+ }
+
+ fun shortFormat(double: Double): String {
+ if (double < 0) return "-" + shortFormat(-double)
+ shortIf(double, 1_000_000_000_000.0, "t") { return it }
+ shortIf(double, 1_000_000_000.0, "b") { return it }
+ shortIf(double, 1_000_000.0, "m") { return it }
+ shortIf(double, 1_000.0, "k") { return it }
+ shortIf(double, 1.0, "") { return it }
+ return double.toString()
+ }
+
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/MoulConfigUtils.kt b/src/main/kotlin/util/MoulConfigUtils.kt
index 62bf3dd..362a4d9 100644
--- a/src/main/kotlin/util/MoulConfigUtils.kt
+++ b/src/main/kotlin/util/MoulConfigUtils.kt
@@ -25,6 +25,7 @@ import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
import net.minecraft.client.gui.DrawContext
import net.minecraft.client.gui.screen.Screen
+import net.minecraft.client.util.InputUtil
import moe.nea.firmament.gui.BarComponent
import moe.nea.firmament.gui.FirmButtonComponent
import moe.nea.firmament.gui.FirmHoverComponent
@@ -257,7 +258,17 @@ object MoulConfigUtils {
keyboardEvent: KeyboardEvent
): Boolean {
val immContext = createInPlaceFullContext(null, IMinecraft.instance.mouseX, IMinecraft.instance.mouseY)
- return component.keyboardEvent(keyboardEvent, immContext.translated(x, y, w, h))
+ if (component.keyboardEvent(keyboardEvent, immContext.translated(x, y, w, h)))
+ return true
+ if (component.context.getFocusedElement() != null) {
+ if (keyboardEvent is KeyboardEvent.KeyPressed
+ && keyboardEvent.pressed && keyboardEvent.keycode == InputUtil.GLFW_KEY_ESCAPE
+ ) {
+ component.context.setFocusedElement(null)
+ }
+ return true
+ }
+ return false
}
fun clickMCComponentInPlace(
diff --git a/src/main/kotlin/util/SBData.kt b/src/main/kotlin/util/SBData.kt
index 051d070..b2f9449 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,75 @@ 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
+ get() {
+ // TODO: allow unfiltered access to this somehow
+ if (!isOnSkyblock) return null
+ return field
+ }
- 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
+ val oldProfileId = profileId
+ locraw = Locraw(it.serverName,
+ it.serverType.getOrNull()?.name?.uppercase(),
+ it.mode.getOrNull(),
+ it.map.getOrNull())
+ SkyblockServerUpdateEvent.publish(SkyblockServerUpdateEvent(lastLocraw, locraw))
+ if(oldProfileId != profileId) {
+ ProfileSwitchEvent.publish(ProfileSwitchEvent(oldProfileId, profileId))
+ }
+ 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/ScoreboardUtil.kt b/src/main/kotlin/util/ScoreboardUtil.kt
index 4311971..0970892 100644
--- a/src/main/kotlin/util/ScoreboardUtil.kt
+++ b/src/main/kotlin/util/ScoreboardUtil.kt
@@ -1,8 +1,6 @@
-
-
package moe.nea.firmament.util
-import java.util.*
+import java.util.Optional
import net.minecraft.client.gui.hud.InGameHud
import net.minecraft.scoreboard.ScoreboardDisplaySlot
import net.minecraft.scoreboard.Team
@@ -10,36 +8,48 @@ import net.minecraft.text.StringVisitable
import net.minecraft.text.Style
import net.minecraft.text.Text
import net.minecraft.util.Formatting
+import moe.nea.firmament.annotations.Subscribe
+import moe.nea.firmament.events.TickEvent
-fun getScoreboardLines(): List<Text> {
- val scoreboard = MC.player?.scoreboard ?: return listOf()
- val activeObjective = scoreboard.getObjectiveForSlot(ScoreboardDisplaySlot.SIDEBAR) ?: return listOf()
- return scoreboard.getScoreboardEntries(activeObjective)
- .filter { !it.hidden() }
- .sortedWith(InGameHud.SCOREBOARD_ENTRY_COMPARATOR)
- .take(15).map {
- val team = scoreboard.getScoreHolderTeam(it.owner)
- val text = it.name()
- Team.decorateName(team, text)
- }
-}
+object ScoreboardUtil {
+ var scoreboardLines: List<Text> = listOf()
+ var simplifiedScoreboardLines: List<String> = listOf()
+ @Subscribe
+ fun onTick(event: TickEvent) {
+ scoreboardLines = getScoreboardLinesUncached()
+ simplifiedScoreboardLines = scoreboardLines.map { it.unformattedString }
+ }
+
+ private fun getScoreboardLinesUncached(): List<Text> {
+ val scoreboard = MC.player?.scoreboard ?: return listOf()
+ val activeObjective = scoreboard.getObjectiveForSlot(ScoreboardDisplaySlot.SIDEBAR) ?: return listOf()
+ return scoreboard.getScoreboardEntries(activeObjective)
+ .filter { !it.hidden() }
+ .sortedWith(InGameHud.SCOREBOARD_ENTRY_COMPARATOR)
+ .take(15).map {
+ val team = scoreboard.getScoreHolderTeam(it.owner)
+ val text = it.name()
+ Team.decorateName(team, text)
+ }
+ }
+}
fun Text.formattedString(): String {
- val sb = StringBuilder()
- visit(StringVisitable.StyledVisitor<Unit> { style, string ->
- val c = Formatting.byName(style.color?.name)
- if (c != null) {
- sb.append("§${c.code}")
- }
- if (style.isUnderlined) {
- sb.append("§n")
- }
- if (style.isBold) {
- sb.append("§l")
- }
- sb.append(string)
- Optional.empty()
- }, Style.EMPTY)
- return sb.toString().replace("§[^a-f0-9]".toRegex(), "")
+ val sb = StringBuilder()
+ visit(StringVisitable.StyledVisitor<Unit> { style, string ->
+ val c = Formatting.byName(style.color?.name)
+ if (c != null) {
+ sb.append("§${c.code}")
+ }
+ if (style.isUnderlined) {
+ sb.append("§n")
+ }
+ if (style.isBold) {
+ sb.append("§l")
+ }
+ sb.append(string)
+ Optional.empty()
+ }, Style.EMPTY)
+ return sb.toString().replace("§[^a-f0-9]".toRegex(), "")
}
diff --git a/src/main/kotlin/util/SkyBlockIsland.kt b/src/main/kotlin/util/SkyBlockIsland.kt
index c42a55c..a86543c 100644
--- a/src/main/kotlin/util/SkyBlockIsland.kt
+++ b/src/main/kotlin/util/SkyBlockIsland.kt
@@ -35,6 +35,8 @@ private constructor(
val PRIVATE_ISLAND = forMode("dynamic")
val RIFT = forMode("rift")
val MINESHAFT = forMode("mineshaft")
+ val GARDEN = forMode("garden")
+ val DUNGEON = forMode("dungeon")
}
val userFriendlyName
diff --git a/src/main/kotlin/util/SkyblockId.kt b/src/main/kotlin/util/SkyblockId.kt
index 1c1aa77..a99afda 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())
@@ -116,6 +119,12 @@ val ItemStack.extraAttributes: NbtCompound
return customData.nbt
}
+fun ItemStack.modifyExtraAttributes(block: (NbtCompound) -> Unit) {
+ val baseNbt = get(DataComponentTypes.CUSTOM_DATA)?.copyNbt() ?: NbtCompound()
+ block(baseNbt)
+ set(DataComponentTypes.CUSTOM_DATA, NbtComponent.of(baseNbt))
+}
+
val ItemStack.skyblockUUIDString: String?
get() = extraAttributes.getString("uuid")?.takeIf { it.isNotBlank() }
@@ -125,10 +134,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/IntrospectableItemModelManager.kt b/src/main/kotlin/util/mc/IntrospectableItemModelManager.kt
new file mode 100644
index 0000000..e546fd3
--- /dev/null
+++ b/src/main/kotlin/util/mc/IntrospectableItemModelManager.kt
@@ -0,0 +1,7 @@
+package moe.nea.firmament.util.mc
+
+import net.minecraft.util.Identifier
+
+interface IntrospectableItemModelManager {
+ fun hasModel_firmament(identifier: Identifier): Boolean
+}
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/render/TranslatedScissors.kt b/src/main/kotlin/util/render/TranslatedScissors.kt
index c1e6544..8f8bdcf 100644
--- a/src/main/kotlin/util/render/TranslatedScissors.kt
+++ b/src/main/kotlin/util/render/TranslatedScissors.kt
@@ -1,11 +1,15 @@
package moe.nea.firmament.util.render
+import org.joml.Matrix4f
import org.joml.Vector4f
import net.minecraft.client.gui.DrawContext
fun DrawContext.enableScissorWithTranslation(x1: Float, y1: Float, x2: Float, y2: Float) {
- val pMat = matrices.peek().positionMatrix
+ enableScissor(x1.toInt(), y1.toInt(), x2.toInt(), y2.toInt())
+}
+fun DrawContext.enableScissorWithoutTranslation(x1: Float, y1: Float, x2: Float, y2: Float) {
+ val pMat = matrices.peek().positionMatrix.invert(Matrix4f())
val target = Vector4f()
target.set(x1, y1, 0f, 1f)
diff --git a/src/main/kotlin/util/skyblock/DungeonUtil.kt b/src/main/kotlin/util/skyblock/DungeonUtil.kt
new file mode 100644
index 0000000..488b158
--- /dev/null
+++ b/src/main/kotlin/util/skyblock/DungeonUtil.kt
@@ -0,0 +1,33 @@
+package moe.nea.firmament.util.skyblock
+
+import moe.nea.firmament.util.SBData
+import moe.nea.firmament.util.ScoreboardUtil
+import moe.nea.firmament.util.SkyBlockIsland
+import moe.nea.firmament.util.TIME_PATTERN
+
+object DungeonUtil {
+ val isInDungeonIsland get() = SBData.skyblockLocation == SkyBlockIsland.DUNGEON
+ private val timeElapsedRegex = "Time Elapsed: $TIME_PATTERN".toRegex()
+ val isInActiveDungeon get() = isInDungeonIsland && ScoreboardUtil.simplifiedScoreboardLines.any { it.matches(
+ timeElapsedRegex) }
+
+/*Title:
+
+§f§lSKYBLOCK§B§L CO-OP
+
+' Late Spring 7th'
+' §75:20am'
+' §7⏣ §cThe Catacombs §7(M3)'
+' §7♲ §7Ironman'
+' '
+'Keys: §c■ §c✗ §8■ §a1x'
+'Time Elapsed: §a46s'
+'Cleared: §660% §8(105)'
+' '
+'§e[B] §b151_Dragon §e2,062§c❤'
+'§e[A] §6Lennart0312 §a17,165§c'
+'§e[T] §b187i §a14,581§c❤'
+'§e[H] §bFlameeke §a8,998§c❤'
+' '
+'§ewww.hypixel.net'*/
+}
diff --git a/src/main/kotlin/util/skyblock/ItemType.kt b/src/main/kotlin/util/skyblock/ItemType.kt
index 6ddb077..7a776b5 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).
@@ -40,6 +65,10 @@ value class ItemType private constructor(val name: String) {
val PET = ofName("PET")
}
+ val dungeonVariant get() = ofName("DUNGEON $name")
+
+ val isDungeon get() = name.startsWith("DUNGEON ")
+
override fun toString(): String {
return name
}
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..c295ae0 100644
--- a/src/main/kotlin/util/textutil.kt
+++ b/src/main/kotlin/util/textutil.kt
@@ -1,72 +1,18 @@
package moe.nea.firmament.util
+import java.util.Optional
import net.minecraft.text.ClickEvent
import net.minecraft.text.MutableText
+import net.minecraft.text.OrderedText
import net.minecraft.text.PlainTextContent
+import net.minecraft.text.StringVisitable
+import net.minecraft.text.Style
import net.minecraft.text.Text
import net.minecraft.text.TextColor
import net.minecraft.text.TranslatableTextContent
import net.minecraft.util.Formatting
-import moe.nea.firmament.Firmament
-
-
-class TextMatcher(text: Text) {
- data class State(
- var iterator: MutableList<Text>,
- var currentText: Text?,
- var offset: Int,
- var textContent: String,
- )
-
- var state = State(
- mutableListOf(text),
- null,
- 0,
- ""
- )
-
- fun pollChunk(): Boolean {
- val firstOrNull = state.iterator.removeFirstOrNull() ?: return false
- state.offset = 0
- state.currentText = firstOrNull
- state.textContent = when (val content = firstOrNull.content) {
- is PlainTextContent.Literal -> content.string
- else -> {
- Firmament.logger.warn("TextContent of type ${content.javaClass} not understood.")
- return false
- }
- }
- state.iterator.addAll(0, firstOrNull.siblings)
- return true
- }
-
- fun pollChunks(): Boolean {
- while (state.offset !in state.textContent.indices) {
- if (!pollChunk()) {
- return false
- }
- }
- return true
- }
-
- fun pollChar(): Char? {
- if (!pollChunks()) return null
- return state.textContent[state.offset++]
- }
- fun expectString(string: String): Boolean {
- var found = ""
- while (found.length < string.length) {
- if (!pollChunks()) return false
- val takeable = state.textContent.drop(state.offset).take(string.length - found.length)
- state.offset += takeable.length
- found += takeable
- }
- return found == string
- }
-}
-
val formattingChars = "kmolnrKMOLNR".toSet()
fun CharSequence.removeColorCodes(keepNonColorCodes: Boolean = false): String {
var nextParagraph = indexOf('§')
@@ -89,6 +35,47 @@ fun CharSequence.removeColorCodes(keepNonColorCodes: Boolean = false): String {
return stringBuffer.toString()
}
+fun OrderedText.reconstitute(): MutableText {
+ val base = Text.literal("")
+ base.setStyle(Style.EMPTY.withItalic(false))
+ var lastColorCode = Style.EMPTY
+ val text = StringBuilder()
+ this.accept { index, style, codePoint ->
+ if (style != lastColorCode) {
+ if (text.isNotEmpty())
+ base.append(Text.literal(text.toString()).setStyle(lastColorCode))
+ lastColorCode = style
+ text.clear()
+ }
+ text.append(codePoint.toChar())
+ true
+ }
+ if (text.isNotEmpty())
+ base.append(Text.literal(text.toString()).setStyle(lastColorCode))
+ return base
+
+}
+fun StringVisitable.reconstitute(): MutableText {
+ val base = Text.literal("")
+ base.setStyle(Style.EMPTY.withItalic(false))
+ var lastColorCode = Style.EMPTY
+ val text = StringBuilder()
+ this.visit({ style, string ->
+ if (style != lastColorCode) {
+ if (text.isNotEmpty())
+ base.append(Text.literal(text.toString()).setStyle(lastColorCode))
+ lastColorCode = style
+ text.clear()
+ }
+ text.append(string)
+ Optional.empty<Unit>()
+ }, Style.EMPTY)
+ if (text.isNotEmpty())
+ base.append(Text.literal(text.toString()).setStyle(lastColorCode))
+ return base
+
+}
+
val Text.unformattedString: String
get() = string.removeColorCodes() // TODO: maybe shortcircuit this with .visit
@@ -133,7 +120,9 @@ 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.darkGrey() = withColor(Formatting.DARK_GRAY)
fun MutableText.red() = withColor(Formatting.RED)
fun MutableText.white() = withColor(Formatting.WHITE)
fun MutableText.bold(): MutableText = styled { it.withBold(true) }
@@ -142,11 +131,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) {