diff options
author | Linnea Gräf <nea@nea.moe> | 2024-06-14 18:34:40 +0200 |
---|---|---|
committer | Linnea Gräf <nea@nea.moe> | 2024-06-14 18:34:40 +0200 |
commit | e4bd69a0569b4ccc49b9e4b89998220bf4bfe25a (patch) | |
tree | 2cb89c55c9c63cbb115cfeaff1848f301c9a461c /src/main/kotlin/moe | |
parent | cd1826a49822e7be0fb583e7b540270560fb657d (diff) | |
download | Firmament-e4bd69a0569b4ccc49b9e4b89998220bf4bfe25a.tar.gz Firmament-e4bd69a0569b4ccc49b9e4b89998220bf4bfe25a.tar.bz2 Firmament-e4bd69a0569b4ccc49b9e4b89998220bf4bfe25a.zip |
Add shiny pig tracker
Diffstat (limited to 'src/main/kotlin/moe')
11 files changed, 436 insertions, 3 deletions
diff --git a/src/main/kotlin/moe/nea/firmament/commands/rome.kt b/src/main/kotlin/moe/nea/firmament/commands/rome.kt index 74391a6..dc46f1f 100644 --- a/src/main/kotlin/moe/nea/firmament/commands/rome.kt +++ b/src/main/kotlin/moe/nea/firmament/commands/rome.kt @@ -9,7 +9,7 @@ package moe.nea.firmament.commands import com.mojang.brigadier.CommandDispatcher import com.mojang.brigadier.arguments.StringArgumentType.string -import io.ktor.client.statement.* +import io.ktor.client.statement.bodyAsText import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource import net.minecraft.text.Text import moe.nea.firmament.apis.UrsaManager @@ -195,6 +195,13 @@ fun firmamentCommand() = literal("firmament") { FairySouls.TConfig.showConfigEditor() } } + thenLiteral("simulate") { + thenArgument("message", RestArgumentType) { message -> + thenExecute { + MC.instance.messageHandler.onGameMessage(Text.literal(get(message)), false) + } + } + } thenLiteral("sbdata") { thenExecute { source.sendFeedback(Text.stringifiedTranslatable("firmament.sbinfo.profile", SBData.profileId)) diff --git a/src/main/kotlin/moe/nea/firmament/events/EntityDespawnEvent.kt b/src/main/kotlin/moe/nea/firmament/events/EntityDespawnEvent.kt new file mode 100644 index 0000000..c744729 --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/events/EntityDespawnEvent.kt @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.events + +import net.minecraft.entity.Entity + +data class EntityDespawnEvent( + val entity: Entity?, val entityId: Int, + val reason: Entity.RemovalReason, +) : FirmamentEvent() { + companion object: FirmamentEventBus<EntityDespawnEvent>() +} diff --git a/src/main/kotlin/moe/nea/firmament/events/EntityInteractionEvent.kt b/src/main/kotlin/moe/nea/firmament/events/EntityInteractionEvent.kt new file mode 100644 index 0000000..fe16868 --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/events/EntityInteractionEvent.kt @@ -0,0 +1,34 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.events + +import net.minecraft.entity.Entity +import net.minecraft.util.Hand + +data class EntityInteractionEvent( + val kind: InteractionKind, + val entity: Entity, + val hand: Hand, +) : FirmamentEvent() { + companion object : FirmamentEventBus<EntityInteractionEvent>() + enum class InteractionKind { + /** + * Is sent when left-clicking an entity + */ + ATTACK, + + /** + * Is a fallback when [INTERACT_AT_LOCATION] fails + */ + INTERACT, + + /** + * Is tried first on right click + */ + INTERACT_AT_LOCATION, + } +} diff --git a/src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt b/src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt index 4c1fde8..f2b2d25 100644 --- a/src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt +++ b/src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt @@ -22,6 +22,7 @@ import moe.nea.firmament.features.debug.DeveloperFeatures import moe.nea.firmament.features.debug.MinorTrolling import moe.nea.firmament.features.debug.PowerUserTools import moe.nea.firmament.features.diana.DianaWaypoints +import moe.nea.firmament.features.events.anniversity.AnniversaryFeatures import moe.nea.firmament.features.fixes.CompatibliltyFeatures import moe.nea.firmament.features.fixes.Fixes import moe.nea.firmament.features.inventory.CraftingOverlay @@ -70,6 +71,7 @@ object FeatureManager : DataHolder<FeatureManager.Config>(serializer(), "feature loadFeature(ChatLinks) loadFeature(InventoryButtons) loadFeature(CompatibliltyFeatures) + loadFeature(AnniversaryFeatures) loadFeature(QuickCommands) loadFeature(SaveCursorPosition) loadFeature(CustomSkyBlockTextures) diff --git a/src/main/kotlin/moe/nea/firmament/features/debug/PowerUserTools.kt b/src/main/kotlin/moe/nea/firmament/features/debug/PowerUserTools.kt index d530487..17e8253 100644 --- a/src/main/kotlin/moe/nea/firmament/features/debug/PowerUserTools.kt +++ b/src/main/kotlin/moe/nea/firmament/features/debug/PowerUserTools.kt @@ -10,12 +10,16 @@ package moe.nea.firmament.features.debug import net.minecraft.block.SkullBlock import net.minecraft.block.entity.SkullBlockEntity import net.minecraft.component.DataComponentTypes +import net.minecraft.entity.Entity +import net.minecraft.entity.LivingEntity import net.minecraft.item.ItemStack import net.minecraft.item.Items import net.minecraft.text.Text import net.minecraft.util.hit.BlockHitResult +import net.minecraft.util.hit.EntityHitResult import net.minecraft.util.hit.HitResult import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.events.CommandEvent import moe.nea.firmament.events.CustomItemModelEvent import moe.nea.firmament.events.HandledScreenKeyPressedEvent import moe.nea.firmament.events.ItemTooltipEvent @@ -41,6 +45,7 @@ object PowerUserTools : FirmamentFeature { val copyTexturePackId by keyBindingWithDefaultUnbound("copy-texture-pack-id") val copyNbtData by keyBindingWithDefaultUnbound("copy-nbt-data") val copySkullTexture by keyBindingWithDefaultUnbound("copy-skull-texture") + val copyEntityData by keyBindingWithDefaultUnbound("entity-data") } override val config @@ -65,6 +70,37 @@ object PowerUserTools : FirmamentFeature { } } + fun debugFormat(itemStack: ItemStack): Text { + return Text.literal(itemStack.skyBlockId?.toString() ?: itemStack.toString()) + } + + @Subscribe + fun onEntityInfo(event: WorldKeyboardEvent) { + if (!event.matches(TConfig.copyEntityData)) return + val target = (MC.instance.crosshairTarget as? EntityHitResult)?.entity + if (target == null) { + MC.sendChat(Text.translatable("firmament.poweruser.entity.fail")) + return + } + showEntity(target) + } + + fun showEntity(target: Entity) { + MC.sendChat(Text.translatable("firmament.poweruser.entity.type", target.type)) + MC.sendChat(Text.translatable("firmament.poweruser.entity.name", target.name)) + if (target is LivingEntity) { + MC.sendChat(Text.translatable("firmament.poweruser.entity.armor")) + for (armorItem in target.armorItems) { + MC.sendChat(Text.translatable("firmament.poweruser.entity.armor.item", debugFormat(armorItem))) + } + } + MC.sendChat(Text.stringifiedTranslatable("firmament.poweruser.entity.passengers", target.passengerList.size)) + target.passengerList.forEach { + showEntity(target) + } + } + + @Subscribe fun copyInventoryInfo(it: HandledScreenKeyPressedEvent) { if (it.screen !is AccessorHandledScreen) return diff --git a/src/main/kotlin/moe/nea/firmament/features/events/anniversity/AnniversaryFeatures.kt b/src/main/kotlin/moe/nea/firmament/features/events/anniversity/AnniversaryFeatures.kt new file mode 100644 index 0000000..5735e76 --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/features/events/anniversity/AnniversaryFeatures.kt @@ -0,0 +1,229 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.features.events.anniversity + +import io.github.notenoughupdates.moulconfig.observer.ObservableList +import io.github.notenoughupdates.moulconfig.xml.Bind +import moe.nea.jarvis.api.Point +import kotlin.time.Duration.Companion.seconds +import net.minecraft.entity.passive.PigEntity +import net.minecraft.util.math.BlockPos +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.events.EntityInteractionEvent +import moe.nea.firmament.events.ProcessChatEvent +import moe.nea.firmament.events.TickEvent +import moe.nea.firmament.events.WorldReadyEvent +import moe.nea.firmament.features.FirmamentFeature +import moe.nea.firmament.gui.config.ManagedConfig +import moe.nea.firmament.gui.hud.MoulConfigHud +import moe.nea.firmament.rei.SBItemEntryDefinition +import moe.nea.firmament.repo.ItemNameLookup +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.SHORT_NUMBER_FORMAT +import moe.nea.firmament.util.SkyblockId +import moe.nea.firmament.util.TimeMark +import moe.nea.firmament.util.parseShortNumber +import moe.nea.firmament.util.useMatch + +object AnniversaryFeatures : FirmamentFeature { + override val identifier: String + get() = "anniversary" + + object TConfig : ManagedConfig(identifier) { + val enableShinyPigTracker by toggle("shiny-pigs") {true} + val trackPigCooldown by position("pig-hud", 200, 300) { Point(0.1, 0.2) } + } + + override val config: ManagedConfig? + get() = TConfig + + data class ClickedPig( + val clickedAt: TimeMark, + val startLocation: BlockPos, + val pigEntity: PigEntity + ) { + @Bind("timeLeft") + fun getTimeLeft(): Double = 1 - clickedAt.passedTime() / pigDuration + } + + val clickedPigs = ObservableList<ClickedPig>(mutableListOf()) + var lastClickedPig: PigEntity? = null + + val pigDuration = 90.seconds + + @Subscribe + fun onTick(event: TickEvent) { + clickedPigs.removeIf { it.clickedAt.passedTime() > pigDuration } + } + + val pattern = "SHINY! You extracted (?<reward>.*) from the piglet's orb!".toPattern() + + @Subscribe + fun onChat(event: ProcessChatEvent) { + if(!TConfig.enableShinyPigTracker)return + if (event.unformattedString == "Oink! Bring the pig back to the Shiny Orb!") { + val pig = lastClickedPig ?: return + // TODO: store proper location based on the orb location, maybe + val startLocation = pig.blockPos ?: return + clickedPigs.add(ClickedPig(TimeMark.now(), startLocation, pig)) + lastClickedPig = null + } + if (event.unformattedString == "SHINY! The orb is charged! Click on it for loot!") { + val player = MC.player ?: return + val lowest = + clickedPigs.minByOrNull { it.startLocation.getSquaredDistance(player.pos) } ?: return + clickedPigs.remove(lowest) + } + pattern.useMatch(event.unformattedString) { + val reward = group("reward") + val parsedReward = parseReward(reward) + addReward(parsedReward) + PigCooldown.rewards.atOnce { + PigCooldown.rewards.clear() + rewards.mapTo(PigCooldown.rewards) { PigCooldown.DisplayReward(it) } + } + } + } + + fun addReward(reward: Reward) { + val it = rewards.listIterator() + while (it.hasNext()) { + val merged = reward.mergeWith(it.next()) ?: continue + it.set(merged) + return + } + rewards.add(reward) + } + + val rewards = mutableListOf<Reward>() + + fun <T> ObservableList<T>.atOnce(block: () -> Unit) { + val oldObserver = observer + observer = null + block() + observer = oldObserver + update() + } + + sealed interface Reward { + fun mergeWith(other: Reward): Reward? + data class EXP(val amount: Double, val skill: String) : Reward { + override fun mergeWith(other: Reward): Reward? { + if (other is EXP && other.skill == skill) + return EXP(amount + other.amount, skill) + return null + } + } + + data class Coins(val amount: Double) : Reward { + override fun mergeWith(other: Reward): Reward? { + if (other is Coins) + return Coins(other.amount + amount) + return null + } + } + + data class Items(val amount: Int, val item: SkyblockId) : Reward { + override fun mergeWith(other: Reward): Reward? { + if (other is Items && other.item == item) + return Items(amount + other.amount, item) + return null + } + } + + data class Unknown(val text: String) : Reward { + override fun mergeWith(other: Reward): Reward? { + return null + } + } + } + + val expReward = "\\+(?<exp>$SHORT_NUMBER_FORMAT) (?<kind>[^ ]+) XP".toPattern() + val coinReward = "\\+(?<amount>$SHORT_NUMBER_FORMAT) coins".toPattern() + val itemReward = "(?:(?<amount>[0-9]+)x )?(?<name>.*)".toPattern() + fun parseReward(string: String): Reward { + expReward.useMatch<Unit>(string) { + val exp = parseShortNumber(group("exp")) + val kind = group("kind") + return Reward.EXP(exp, kind) + } + coinReward.useMatch<Unit>(string) { + val coins = parseShortNumber(group("amount")) + return Reward.Coins(coins) + } + itemReward.useMatch(string) { + val amount = group("amount")?.toIntOrNull() ?: 1 + val name = group("name") + val item = ItemNameLookup.guessItemByName(name, false) ?: return@useMatch + return Reward.Items(amount, item) + } + return Reward.Unknown(string) + } + + @Subscribe + fun onWorldClear(event: WorldReadyEvent) { + lastClickedPig = null + clickedPigs.clear() + } + + @Subscribe + fun onEntityClick(event: EntityInteractionEvent) { + if (event.entity is PigEntity) { + lastClickedPig = event.entity + } + } + + @Subscribe + fun init(event: WorldReadyEvent) { + PigCooldown.forceInit() + } + + object PigCooldown : MoulConfigHud("anniversary_pig", TConfig.trackPigCooldown) { + override fun shouldRender(): Boolean { + return clickedPigs.isNotEmpty() && TConfig.enableShinyPigTracker + } + + @Bind("pigs") + fun getPigs() = clickedPigs + + class DisplayReward(val backedBy: Reward) { + @Bind + fun count(): String { + return when (backedBy) { + is Reward.Coins -> backedBy.amount + is Reward.EXP -> backedBy.amount + is Reward.Items -> backedBy.amount + is Reward.Unknown -> 0 + }.toString() + } + + val itemStack = if (backedBy is Reward.Items) { + SBItemEntryDefinition.getEntry(backedBy.item, backedBy.amount) + } else { + SBItemEntryDefinition.getEntry(SkyblockId.NULL) + } + + @Bind + fun name(): String { + return when (backedBy) { + is Reward.Coins -> "Coins" + is Reward.EXP -> backedBy.skill + is Reward.Items -> itemStack.value.asItemStack().name.string + is Reward.Unknown -> backedBy.text + } + } + + @Bind + fun isKnown() = backedBy !is Reward.Unknown + } + + @get:Bind("rewards") + val rewards = ObservableList<DisplayReward>(mutableListOf()) + + } + +} diff --git a/src/main/kotlin/moe/nea/firmament/gui/BarComponent.kt b/src/main/kotlin/moe/nea/firmament/gui/BarComponent.kt index a1eb23c..91b5735 100644 --- a/src/main/kotlin/moe/nea/firmament/gui/BarComponent.kt +++ b/src/main/kotlin/moe/nea/firmament/gui/BarComponent.kt @@ -58,7 +58,7 @@ class BarComponent( ScreenDrawing.texturedRect(context, x, y, 4, 8, texture, emptyColor.color) return } - val increasePerPixel = (sectionEnd - sectionStart / 4) + val increasePerPixel = (sectionEnd - sectionStart) / width var valueAtPixel = sectionStart for (i in (0 until width)) { ScreenDrawing.texturedRect( diff --git a/src/main/kotlin/moe/nea/firmament/gui/hud/MoulConfigHud.kt b/src/main/kotlin/moe/nea/firmament/gui/hud/MoulConfigHud.kt index ec884bb..afc8740 100644 --- a/src/main/kotlin/moe/nea/firmament/gui/hud/MoulConfigHud.kt +++ b/src/main/kotlin/moe/nea/firmament/gui/hud/MoulConfigHud.kt @@ -30,11 +30,16 @@ abstract class MoulConfigHud( private var fragment: GuiContext? = null + fun forceInit() { + + } + open fun shouldRender(): Boolean { return true } init { + require(name.matches("^[a-z_/]+$".toRegex())) HudRenderEvent.subscribe { if (!shouldRender()) return@subscribe val renderContext = componentWrapper.createContext(it.context) diff --git a/src/main/kotlin/moe/nea/firmament/repo/ItemNameLookup.kt b/src/main/kotlin/moe/nea/firmament/repo/ItemNameLookup.kt new file mode 100644 index 0000000..4bdf616 --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/repo/ItemNameLookup.kt @@ -0,0 +1,103 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.repo + +import io.github.moulberry.repo.IReloadable +import io.github.moulberry.repo.NEURepository +import io.github.moulberry.repo.data.NEUItem +import java.util.NavigableMap +import java.util.TreeMap +import moe.nea.firmament.util.SkyblockId +import moe.nea.firmament.util.removeColorCodes +import moe.nea.firmament.util.skyblockId + +object ItemNameLookup : IReloadable { + + fun getItemNameChunks(name: String): Set<String> { + return name.removeColorCodes().split(" ").filterTo(mutableSetOf()) { it.isNotBlank() } + } + + var nameMap: NavigableMap<String, out Set<SkyblockId>> = TreeMap() + + override fun reload(repository: NEURepository) { + val nameMap = TreeMap<String, MutableSet<SkyblockId>>() + repository.items.items.values.forEach { item -> + getAllNamesForItem(item).forEach { name -> + val chunks = getItemNameChunks(name) + chunks.forEach { chunk -> + val set = nameMap.getOrPut(chunk, ::mutableSetOf) + set.add(item.skyblockId) + } + } + } + this.nameMap = nameMap + } + + fun getAllNamesForItem(item: NEUItem): Set<String> { + val names = mutableSetOf<String>() + names.add(item.displayName) + if (item.displayName.contains("Enchanted Book")) { + val enchantName = item.lore.firstOrNull() + if (enchantName != null) { + names.add(enchantName) + } + } + return names + } + + fun findItemCandidatesByName(name: String): MutableSet<SkyblockId> { + val candidates = mutableSetOf<SkyblockId>() + for (chunk in getItemNameChunks(name)) { + val set = nameMap[chunk] ?: emptySet() + candidates.addAll(set) + } + return candidates + } + + + fun guessItemByName( + /** + * The display name of the item. Color codes will be ignored. + */ + name: String, + /** + * Whether the [name] may contain other text, such as reforges, master stars and such. + */ + mayBeMangled: Boolean + ): SkyblockId? { + val cleanName = name.removeColorCodes() + return findBestItemFromCandidates( + findItemCandidatesByName(cleanName), + cleanName, + true + ) + } + + fun findBestItemFromCandidates( + candidates: Iterable<SkyblockId>, + name: String, mayBeMangled: Boolean + ): SkyblockId? { + val expectedClean = name.removeColorCodes() + var bestMatch: SkyblockId? = null + var bestMatchLength = -1 + for (candidate in candidates) { + val item = RepoManager.getNEUItem(candidate) ?: continue + for (name in getAllNamesForItem(item)) { + val actualClean = name.removeColorCodes() + val matches = if (mayBeMangled) expectedClean == actualClean + else expectedClean.contains(actualClean) + if (!matches) continue + if (actualClean.length > bestMatchLength) { + bestMatch = candidate + bestMatchLength = actualClean.length + } + } + } + return bestMatch + } + +} diff --git a/src/main/kotlin/moe/nea/firmament/repo/RepoManager.kt b/src/main/kotlin/moe/nea/firmament/repo/RepoManager.kt index a1e73b1..db17e6a 100644 --- a/src/main/kotlin/moe/nea/firmament/repo/RepoManager.kt +++ b/src/main/kotlin/moe/nea/firmament/repo/RepoManager.kt @@ -63,6 +63,7 @@ object RepoManager { val neuRepo: NEURepository = NEURepository.of(RepoDownloadManager.repoSavedLocation).apply { registerReloadListener(ItemCache) registerReloadListener(ExpLadders) + registerReloadListener(ItemNameLookup) registerReloadListener { Firmament.coroutineScope.launch(MinecraftDispatcher) { if (!trySendClientboundUpdateRecipesPacket()) { diff --git a/src/main/kotlin/moe/nea/firmament/util/MoulConfigUtils.kt b/src/main/kotlin/moe/nea/firmament/util/MoulConfigUtils.kt index 0b56c7c..4d3221f 100644 --- a/src/main/kotlin/moe/nea/firmament/util/MoulConfigUtils.kt +++ b/src/main/kotlin/moe/nea/firmament/util/MoulConfigUtils.kt @@ -18,7 +18,7 @@ import org.w3c.dom.Element import moe.nea.firmament.gui.BarComponent object MoulConfigUtils { - val firmUrl = "http://nea.moe/Firmament" + val firmUrl = "http://firmament.nea.moe/moulconfig" val universe = XMLUniverse.getDefaultUniverse().also { uni -> uni.registerMapper(java.awt.Color::class.java) { if (it.startsWith("#")) { |