diff options
| -rw-r--r-- | src/main/kotlin/features/inventory/TimerInLore.kt | 29 | ||||
| -rw-r--r-- | src/main/kotlin/util/SkyblockId.kt | 44 | ||||
| -rw-r--r-- | src/test/kotlin/util/skyblock/TimestampTest.kt | 28 | ||||
| -rw-r--r-- | src/test/resources/testdata/items/backpack-in-menu.snbt | 122 | ||||
| -rw-r--r-- | translations/en_us.json | 4 |
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", |
