aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinnea Gräf <nea@nea.moe>2025-06-26 19:15:38 +0200
committerLinnea Gräf <nea@nea.moe>2025-06-26 19:15:38 +0200
commita8f8382682fb70791ac5e5601f1c0c149df8c8eb (patch)
tree82d8118f615d6b8c9a7332990df255c9e9467d8c
parent1c5d0df368471031f892330de7628ff78a6204ed (diff)
downloadFirmament-a8f8382682fb70791ac5e5601f1c0c149df8c8eb.tar.gz
Firmament-a8f8382682fb70791ac5e5601f1c0c149df8c8eb.tar.bz2
Firmament-a8f8382682fb70791ac5e5601f1c0c149df8c8eb.zip
feat: add creation timestamp in lore
-rw-r--r--src/main/kotlin/features/inventory/TimerInLore.kt29
-rw-r--r--src/main/kotlin/util/SkyblockId.kt44
-rw-r--r--src/test/kotlin/util/skyblock/TimestampTest.kt28
-rw-r--r--src/test/resources/testdata/items/backpack-in-menu.snbt122
-rw-r--r--translations/en_us.json4
5 files changed, 212 insertions, 15 deletions
diff --git a/src/main/kotlin/features/inventory/TimerInLore.kt b/src/main/kotlin/features/inventory/TimerInLore.kt
index 8eac77f..cc1df9a 100644
--- a/src/main/kotlin/features/inventory/TimerInLore.kt
+++ b/src/main/kotlin/features/inventory/TimerInLore.kt
@@ -16,12 +16,14 @@ import moe.nea.firmament.util.SBData
import moe.nea.firmament.util.aqua
import moe.nea.firmament.util.grey
import moe.nea.firmament.util.mc.displayNameAccordingToNbt
+import moe.nea.firmament.util.timestamp
import moe.nea.firmament.util.tr
import moe.nea.firmament.util.unformattedString
object TimerInLore {
object TConfig : ManagedConfig("lore-timers", Category.INVENTORY) {
val showTimers by toggle("show") { true }
+ val showCreationTimestamp by toggle("show-creation") { true }
val timerFormat by choice("format") { TimerFormat.SOCIALIST }
}
@@ -91,6 +93,14 @@ object TimerInLore {
"(?i)(?:(?<years>[0-9]+) ?(y|years?) )?(?:(?<days>[0-9]+) ?(d|days?))? ?(?:(?<hours>[0-9]+) ?(h|hours?))? ?(?:(?<minutes>[0-9]+) ?(m|minutes?))? ?(?:(?<seconds>[0-9]+) ?(s|seconds?))?\\b".toRegex()
@Subscribe
+ fun creationInLore(event: ItemTooltipEvent) {
+ if (!TConfig.showCreationTimestamp) return
+ val timestamp = event.stack.timestamp ?: return
+ val formattedTimestamp = TConfig.timerFormat.formatter.format(ZonedDateTime.ofInstant(timestamp, ZoneId.systemDefault()))
+ event.lines.add(tr("firmament.lore.creationtimestamp", "Created at: $formattedTimestamp").grey())
+ }
+
+ @Subscribe
fun modifyLore(event: ItemTooltipEvent) {
if (!TConfig.showTimers) return
var lastTimer: ZonedDateTime? = null
@@ -111,9 +121,13 @@ object TimerInLore {
var baseLine = ZonedDateTime.now(SBData.hypixelTimeZone)
if (countdownType.isRelative) {
if (lastTimer == null) {
- event.lines.add(i + 1,
- tr("firmament.loretimer.missingrelative",
- "Found a relative countdown with no baseline (Firmament)").grey())
+ event.lines.add(
+ i + 1,
+ tr(
+ "firmament.loretimer.missingrelative",
+ "Found a relative countdown with no baseline (Firmament)"
+ ).grey()
+ )
continue
}
baseLine = lastTimer
@@ -123,10 +137,11 @@ object TimerInLore {
lastTimer = timer
val localTimer = timer.withZoneSameInstant(ZoneId.systemDefault())
// TODO: install approximate time stabilization algorithm
- event.lines.add(i + 1,
- Text.literal("${countdownType.label}: ")
- .grey()
- .append(Text.literal(TConfig.timerFormat.formatter.format(localTimer)).aqua())
+ event.lines.add(
+ i + 1,
+ Text.literal("${countdownType.label}: ")
+ .grey()
+ .append(Text.literal(TConfig.timerFormat.formatter.format(localTimer)).aqua())
)
}
}
diff --git a/src/main/kotlin/util/SkyblockId.kt b/src/main/kotlin/util/SkyblockId.kt
index af767f8..8ede97e 100644
--- a/src/main/kotlin/util/SkyblockId.kt
+++ b/src/main/kotlin/util/SkyblockId.kt
@@ -6,6 +6,11 @@ import com.mojang.serialization.Codec
import io.github.moulberry.repo.data.NEUIngredient
import io.github.moulberry.repo.data.NEUItem
import io.github.moulberry.repo.data.Rarity
+import java.time.Instant
+import java.time.LocalDateTime
+import java.time.format.DateTimeFormatterBuilder
+import java.time.format.SignStyle
+import java.time.temporal.ChronoField
import java.util.Optional
import java.util.UUID
import kotlinx.serialization.Serializable
@@ -38,13 +43,14 @@ import moe.nea.firmament.util.json.DashlessUUIDSerializer
@Serializable
value class SkyblockId(val neuItem: String) : Comparable<SkyblockId> {
val identifier
- get() = Identifier.of("skyblockitem",
- neuItem.lowercase().replace(";", "__")
- .replace(":", "___")
- .replace(illlegalPathRegex) {
- it.value.toCharArray()
- .joinToString("") { "__" + it.code.toString(16).padStart(4, '0') }
- })
+ get() = Identifier.of(
+ "skyblockitem",
+ neuItem.lowercase().replace(";", "__")
+ .replace(":", "___")
+ .replace(illlegalPathRegex) {
+ it.value.toCharArray()
+ .joinToString("") { "__" + it.code.toString(16).padStart(4, '0') }
+ })
override fun toString(): String {
return neuItem
@@ -137,6 +143,30 @@ fun ItemStack.modifyExtraAttributes(block: (NbtCompound) -> Unit) {
val ItemStack.skyblockUUIDString: String?
get() = extraAttributes.getString("uuid").getOrNull()?.takeIf { it.isNotBlank() }
+private val timestampFormat = //"10/11/21 3:39 PM"
+ DateTimeFormatterBuilder().apply {
+ appendValue(ChronoField.MONTH_OF_YEAR, 2)
+ appendLiteral("/")
+ appendValue(ChronoField.DAY_OF_MONTH, 2)
+ appendLiteral("/")
+ appendValueReduced(ChronoField.YEAR, 2, 2, 1950)
+ appendLiteral(" ")
+ appendValue(ChronoField.HOUR_OF_AMPM, 1, 2, SignStyle.NEVER)
+ appendLiteral(":")
+ appendValue(ChronoField.MINUTE_OF_HOUR, 2)
+ appendLiteral(" ")
+ appendText(ChronoField.AMPM_OF_DAY)
+ }.toFormatter()
+val ItemStack.timestamp
+ get() =
+ extraAttributes.getLong("timestamp").getOrNull()?.let { Instant.ofEpochMilli(it) }
+ ?: extraAttributes.getString("timestamp").getOrNull()?.let {
+ ErrorUtil.catch("Could not parse timestamp $it") {
+ LocalDateTime.from(timestampFormat.parse(it)).atZone(SBData.hypixelTimeZone)
+ .toInstant()
+ }.orNull()
+ }
+
val ItemStack.skyblockUUID: UUID?
get() = skyblockUUIDString?.let { UUID.fromString(it) }
diff --git a/src/test/kotlin/util/skyblock/TimestampTest.kt b/src/test/kotlin/util/skyblock/TimestampTest.kt
new file mode 100644
index 0000000..b960cb9
--- /dev/null
+++ b/src/test/kotlin/util/skyblock/TimestampTest.kt
@@ -0,0 +1,28 @@
+package moe.nea.firmament.test.util.skyblock
+
+import java.time.Instant
+import java.time.ZonedDateTime
+import org.junit.jupiter.api.Assertions
+import org.junit.jupiter.api.Test
+import moe.nea.firmament.test.testutil.ItemResources
+import moe.nea.firmament.util.SBData
+import moe.nea.firmament.util.timestamp
+
+class TimestampTest {
+
+ @Test
+ fun testLongTimestamp() {
+ Assertions.assertEquals(
+ Instant.ofEpochSecond(1658091600),
+ ItemResources.loadItem("hyperion").timestamp
+ )
+ }
+
+ @Test
+ fun testStringTimestamp() {
+ Assertions.assertEquals(
+ ZonedDateTime.of(2021, 10, 11, 15, 39, 0, 0, SBData.hypixelTimeZone).toInstant(),
+ ItemResources.loadItem("backpack-in-menu").timestamp
+ )
+ }
+}
diff --git a/src/test/resources/testdata/items/backpack-in-menu.snbt b/src/test/resources/testdata/items/backpack-in-menu.snbt
new file mode 100644
index 0000000..2f22768
--- /dev/null
+++ b/src/test/resources/testdata/items/backpack-in-menu.snbt
@@ -0,0 +1,122 @@
+{
+ components: {
+ "minecraft:custom_data": {
+ backpack_color: "BROWN",
+ originTag: "CRAFTING_GRID_COLLECT",
+ timestamp: "10/11/21 3:39 PM",
+ uuid: "3d7c83e8-c619-4603-8cfb-c95ceed90864"
+ },
+ "minecraft:custom_name": {
+ extra: [
+ {
+ color: "gold",
+ text: "Backpack Slot 3"
+ }
+ ],
+ italic: 0b,
+ text: ""
+ },
+ "minecraft:lore": [
+ {
+ extra: [
+ {
+ color: "gold",
+ text: "Jumbo Backpack"
+ }
+ ],
+ italic: 0b,
+ text: ""
+ },
+ {
+ extra: [
+ {
+ color: "gray",
+ text: ""
+ },
+ {
+ color: "gray",
+ text: "This backpack has "
+ },
+ {
+ color: "green",
+ text: "45"
+ },
+ {
+ color: "gray",
+ text: " slots."
+ }
+ ],
+ italic: 0b,
+ text: ""
+ },
+ {
+ extra: [
+ " "
+ ],
+ italic: 0b,
+ text: ""
+ },
+ {
+ extra: [
+ {
+ color: "gray",
+ text: ""
+ },
+ {
+ color: "yellow",
+ text: "Left-click to open!"
+ }
+ ],
+ italic: 0b,
+ text: ""
+ },
+ {
+ extra: [
+ {
+ color: "gray",
+ text: ""
+ },
+ {
+ color: "yellow",
+ text: "Right-click to remove!"
+ }
+ ],
+ italic: 0b,
+ text: ""
+ }
+ ],
+ "minecraft:profile": {
+ id: [I;
+ 1252359403,
+ 1319582828,
+ -1927151386,
+ 833492163
+ ],
+ properties: [
+ {
+ name: "textures",
+ signature: "U/49v6SXIw8bAmqM6T7t1BIR736N3Adpx7MlWncnT8zcFEm97zwRx9/tyaUy/XxBHaPGSL6BbgW2TdBtfb9gf0emCAZyWmnzSTtqDGiWpxnQM8v3+gHS8zD7Xrho0a/hU33xTbQ2knj2iRz8C+FReoJFxCjS++aXq6IqliIb3GhqB5b1egaiG2Q3t+yerl2Xue4nhdYM3wtGsYApC/ClR3TEuBcJv1WUVZM8rEoU29pbVnyMCKineG6mIN7W86SmzcT2SF+zMVyD0/mI7R2hRT2lbXnkMpM6FFscdnlvzjjPB9brtAWY7JGJ63b9C+khnvZUlhlQ/3E/08dFnON31VeabJXOmfrbfAgsF0Hgfs7Io+HzoXSXr/FCxNCCFMWlSwORmG2WCT4VRFzG2SThatPVPGJkuR/tLLOLzXo4RKOMzY5EIwa2XSxRUI4+5z2SZY11ofGic3bZD3wvICs2EZ54Pi508ZOda0qI9w5Q/TazC+jX/I5Nq2TLqLj+uU/+UX8eKXvHdk8QpBynyv9SyHo21jVXpiUgL1AsdzBp9cTZHNJuYtBxgDogr3SyAKPmw3BOzVeUi6qW8k4lgtefLKYteVSh52PjFgvQZUR1GNmFaJ+hlgKz8yONp+wXhw3nyL4dMOd2Z/dVVSywBp0tyHuN5l3PfaInK4s8qSydaW0=",
+ value: "ewogICJ0aW1lc3RhbXAiIDogMTcxOTUzODgxNTgyNCwKICAicHJvZmlsZUlkIiA6ICJkOWYxNTlhYWYxZjY0NGZlOTEwOTg0NzI2ZDBjMWJjMCIsCiAgInByb2ZpbGVOYW1lIiA6ICJtYW5vbmFtaXNzaW9uRyIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS81YWQwYjQwNTIxMjYyYjdhM2Y5OWU2M2JkZGQ0YTNlNTQxOTY1Njc3ZTE0MTRlYWZhMTQyZThiYmE5ZGZlNDgxIiwKICAgICAgIm1ldGFkYXRhIiA6IHsKICAgICAgICAibW9kZWwiIDogInNsaW0iCiAgICAgIH0KICAgIH0KICB9Cn0="
+ }
+ ]
+ },
+ "minecraft:tooltip_display": {
+ hidden_components: [
+ "minecraft:jukebox_playable",
+ "minecraft:painting/variant",
+ "minecraft:map_id",
+ "minecraft:fireworks",
+ "minecraft:attribute_modifiers",
+ "minecraft:unbreakable",
+ "minecraft:written_book_content",
+ "minecraft:banner_patterns",
+ "minecraft:trim",
+ "minecraft:potion_contents",
+ "minecraft:block_entity_data",
+ "minecraft:dyed_color"
+ ]
+ }
+ },
+ count: 3,
+ id: "minecraft:player_head"
+}
diff --git a/translations/en_us.json b/translations/en_us.json
index 1ef9a9b..cc7fb39 100644
--- a/translations/en_us.json
+++ b/translations/en_us.json
@@ -173,7 +173,7 @@
"firmament.config.jade-integration.blocks.description": "Show custom block descriptions and hardness levels in Jade.",
"firmament.config.jade-integration.progress": "Enable Custom Mining Progress",
"firmament.config.jade-integration.progress.description": "Show the custom mining progress in Jade, when in a world with mining fatigue.",
- "firmament.config.lore-timers": "Lore Timers",
+ "firmament.config.lore-timers": "Item Timestamps",
"firmament.config.lore-timers.format": "Time Format",
"firmament.config.lore-timers.format.choice.american": "§9Ame§cri§fcan",
"firmament.config.lore-timers.format.choice.local": "System Time Format",
@@ -181,6 +181,8 @@
"firmament.config.lore-timers.format.choice.socialist": "European-ish",
"firmament.config.lore-timers.format.description": "Choose the time format in which resolved timers are displayed.",
"firmament.config.lore-timers.show": "Show Lore Timers",
+ "firmament.config.lore-timers.show-creation": "Show Creation",
+ "firmament.config.lore-timers.show-creation.description": "Shows the creation or craft timestamp of the item. Sometimes this timestamp is retained when upgrading an item, so it isn't necessarily the craft time of this specific item, but rather one of its components.",
"firmament.config.lore-timers.show.description": "Shows when a timer in a lore (such as interest, auction duration) would end.",
"firmament.config.party-commands": "Party Commands",
"firmament.config.party-commands.cooldown": "Cooldown",