diff options
Diffstat (limited to 'src/main/kotlin/moe/nea')
21 files changed, 558 insertions, 23 deletions
diff --git a/src/main/kotlin/moe/nea/ledger/DebouncedValue.kt b/src/main/kotlin/moe/nea/ledger/DebouncedValue.kt new file mode 100644 index 0000000..66fba8d --- /dev/null +++ b/src/main/kotlin/moe/nea/ledger/DebouncedValue.kt @@ -0,0 +1,38 @@ +package moe.nea.ledger + +import kotlin.time.Duration +import kotlin.time.Duration.Companion.nanoseconds +import kotlin.time.Duration.Companion.seconds + +class DebouncedValue<T>(private val value: T) { + companion object { + fun <T> farFuture(): DebouncedValue<T> { + val value = DebouncedValue(Unit) + value.take() + @Suppress("UNCHECKED_CAST") + return value as DebouncedValue<T> + } + } + + val lastSeenAt = System.nanoTime() + val age get() = (System.nanoTime() - lastSeenAt).nanoseconds + var taken = false + private set + + fun get(debounce: Duration): T? { + return if (!taken && age >= debounce) value + else null + } + + fun replace(): T? { + return consume(0.seconds) + } + + fun consume(debounce: Duration): T? { + return get(debounce)?.also { take() } + } + + fun take() { + taken = true + } +}
\ No newline at end of file diff --git a/src/main/kotlin/moe/nea/ledger/ExpiringValue.kt b/src/main/kotlin/moe/nea/ledger/ExpiringValue.kt index dac4751..b50b14e 100644 --- a/src/main/kotlin/moe/nea/ledger/ExpiringValue.kt +++ b/src/main/kotlin/moe/nea/ledger/ExpiringValue.kt @@ -7,8 +7,10 @@ class ExpiringValue<T>(private val value: T) { val lastSeenAt: Long = System.nanoTime() val age get() = (System.nanoTime() - lastSeenAt).nanoseconds var taken = false + private set + fun get(expiry: Duration): T? { - return if (!taken && expiry > age) value + return if (!taken && age < expiry) value else null } @@ -21,7 +23,7 @@ class ExpiringValue<T>(private val value: T) { } } - fun consume(expiry: Duration): T? = get(expiry).also { take() } + fun consume(expiry: Duration): T? = get(expiry)?.also { take() } fun take() { taken = true } diff --git a/src/main/kotlin/moe/nea/ledger/ItemId.kt b/src/main/kotlin/moe/nea/ledger/ItemId.kt index 2b83357..fdfa19f 100644 --- a/src/main/kotlin/moe/nea/ledger/ItemId.kt +++ b/src/main/kotlin/moe/nea/ledger/ItemId.kt @@ -21,6 +21,7 @@ value class ItemId( val GEMSTONE_POWDER = ItemId("SKYBLOCK_POWDER_GEMSTONE") val MITHRIL_POWDER = ItemId("SKYBLOCK_POWDER_MITHRIL") val GOLD_ESSENCE = ItemId("ESSENCE_GOLD") + val DRAGON_ESSENCE = ItemId("ESSENCE_DRAGON") val PELT = ItemId("SKYBLOCK_PELT") val COINS = ItemId("SKYBLOCK_COIN") val FINE_FLOUR = ItemId("FINE_FLOUR") @@ -29,7 +30,10 @@ value class ItemId( val NIL = ItemId("SKYBLOCK_NIL") val ARCHFIEND_LOW_CLASS = ItemId("ARCHFIEND_DICE") val ARCHFIEND_HIGH_CLASS = ItemId("HIGH_CLASS_ARCHFIEND_DICE") + val CAP_EYEDROPS = ItemId("CAPSAICIN_EYEDROPS_NO_CHARGES") val ARCHFIEND_DYE = ItemId("DYE_ARCHFIEND") + val SLEEPING_EYE = ItemId("SLEEPING_EYE") + val SUMMONING_EYE = ItemId("SUMMONING_EYE") val DUNGEON_CHEST_KEY = ItemId("DUNGEON_CHEST_KEY") val BOOSTER_COOKIE = ItemId("BOOSTER_COOKIE") val KISMET_FEATHER = ItemId("KISMET_FEATHER") diff --git a/src/main/kotlin/moe/nea/ledger/ItemIdProvider.kt b/src/main/kotlin/moe/nea/ledger/ItemIdProvider.kt index 72f1d09..4d85713 100644 --- a/src/main/kotlin/moe/nea/ledger/ItemIdProvider.kt +++ b/src/main/kotlin/moe/nea/ledger/ItemIdProvider.kt @@ -105,7 +105,9 @@ class ItemIdProvider { private val coinRegex = "(?<amount>$SHORT_NUMBER_PATTERN) Coins?".toPattern() private val stackedItemRegex = "(?<name>.*) x(?<count>$SHORT_NUMBER_PATTERN)".toPattern() + private val reverseStackedItemRegex = "(?<count>$SHORT_NUMBER_PATTERN)x (?<name>.*)".toPattern() private val essenceRegex = "(?<essence>.*) Essence x(?<count>$SHORT_NUMBER_PATTERN)".toPattern() + private val numberedItemRegex = "(?<count>$SHORT_NUMBER_PATTERN) (?<what>.*)".toPattern() fun findCostItemsFromSpan(lore: List<String>): List<Pair<ItemId, Double>> { return lore.iterator().asSequence() @@ -155,12 +157,27 @@ class ItemIdProvider { parseShortNumber(group("count"))) } stackedItemRegex.useMatcher(properName) { - var item = findForName(group("name"), fallbackToGenerated) + val item = findForName(group("name"), fallbackToGenerated) if (item != null) { val count = parseShortNumber(group("count")) return Pair(item, count) } } + reverseStackedItemRegex.useMatcher(properName) { + val item = findForName(group("name"), fallbackToGenerated) + if (item != null) { + val count = parseShortNumber(group("count")) + return Pair(item, count) + } + } + numberedItemRegex.useMatcher(properName) { + val item = findForName(group("what"), fallbackToGenerated) + if (item != null) { + val count = parseShortNumber(group("count")) + return Pair(item, count) + } + } + return findForName(properName, fallbackToGenerated)?.let { Pair(it, 1.0) } } diff --git a/src/main/kotlin/moe/nea/ledger/Ledger.kt b/src/main/kotlin/moe/nea/ledger/Ledger.kt index bb69621..d2d09eb 100644 --- a/src/main/kotlin/moe/nea/ledger/Ledger.kt +++ b/src/main/kotlin/moe/nea/ledger/Ledger.kt @@ -3,10 +3,13 @@ package moe.nea.ledger import com.google.gson.Gson import io.github.notenoughupdates.moulconfig.managed.ManagedConfig import moe.nea.ledger.config.LedgerConfig +import moe.nea.ledger.config.UpdateUi +import moe.nea.ledger.config.UpdateUiMarker import moe.nea.ledger.database.Database import moe.nea.ledger.events.ChatReceived import moe.nea.ledger.events.LateWorldLoadEvent import moe.nea.ledger.events.RegistrationFinishedEvent +import moe.nea.ledger.events.WorldSwitchEvent import moe.nea.ledger.gen.BuildConfig import moe.nea.ledger.modules.AuctionHouseDetection import moe.nea.ledger.modules.BankDetection @@ -14,8 +17,11 @@ import moe.nea.ledger.modules.BazaarDetection import moe.nea.ledger.modules.BazaarOrderDetection import moe.nea.ledger.modules.BitsDetection import moe.nea.ledger.modules.BitsShopDetection +import moe.nea.ledger.modules.DragonEyePlacementDetection +import moe.nea.ledger.modules.`DragonSacrificeDetection` import moe.nea.ledger.modules.DungeonChestDetection import moe.nea.ledger.modules.ExternalDataProvider +import moe.nea.ledger.modules.EyedropsDetection import moe.nea.ledger.modules.ForgeDetection import moe.nea.ledger.modules.GambleDetection import moe.nea.ledger.modules.KatDetection @@ -23,9 +29,12 @@ import moe.nea.ledger.modules.KuudraChestDetection import moe.nea.ledger.modules.MineshaftCorpseDetection import moe.nea.ledger.modules.MinionDetection import moe.nea.ledger.modules.NpcDetection +import moe.nea.ledger.modules.UpdateChecker import moe.nea.ledger.modules.VisitorDetection import moe.nea.ledger.utils.ErrorUtil +import moe.nea.ledger.utils.MinecraftExecutor import moe.nea.ledger.utils.di.DI +import moe.nea.ledger.utils.di.DIProvider import moe.nea.ledger.utils.network.RequestUtil import net.minecraft.client.Minecraft import net.minecraft.command.ICommand @@ -82,6 +91,9 @@ class Ledger { val logger = LogManager.getLogger("MoneyLedger") val managedConfig = ManagedConfig.create(File("config/money-ledger/config.json"), LedgerConfig::class.java) { checkExpose = false + customProcessor<UpdateUiMarker> { option, ann -> + UpdateUi(option) + } } val gson = Gson() private val tickQueue = ConcurrentLinkedQueue<Runnable>() @@ -100,31 +112,38 @@ class Ledger { di.registerSingleton(this) di.registerSingleton(Minecraft.getMinecraft()) di.registerSingleton(gson) + di.register(LedgerConfig::class.java, DIProvider { managedConfig.instance }) di.registerInjectableClasses( AuctionHouseDetection::class.java, BankDetection::class.java, BazaarDetection::class.java, BazaarOrderDetection::class.java, - DebugDataCommand::class.java, BitsDetection::class.java, BitsShopDetection::class.java, ConfigCommand::class.java, Database::class.java, + DebugDataCommand::class.java, + DragonEyePlacementDetection::class.java, + DragonSacrificeDetection::class.java, DungeonChestDetection::class.java, ErrorUtil::class.java, ExternalDataProvider::class.java, + EyedropsDetection::class.java, + ForgeDetection::class.java, + GambleDetection::class.java, ItemIdProvider::class.java, KatDetection::class.java, KuudraChestDetection::class.java, LedgerLogger::class.java, LogChatCommand::class.java, - MinionDetection::class.java, + MinecraftExecutor::class.java, MineshaftCorpseDetection::class.java, - ForgeDetection::class.java, + MinionDetection::class.java, NpcDetection::class.java, - GambleDetection::class.java, QueryCommand::class.java, RequestUtil::class.java, + TriggerCommand::class.java, + UpdateChecker::class.java, VisitorDetection::class.java, ) val errorUtil = di.provide<ErrorUtil>() @@ -148,6 +167,7 @@ class Ledger { fun worldSwitchEvent(event: EntityJoinWorldEvent) { if (event.entity == Minecraft.getMinecraft().thePlayer) { lastJoin = System.currentTimeMillis() + MinecraftForge.EVENT_BUS.post(WorldSwitchEvent()) } } diff --git a/src/main/kotlin/moe/nea/ledger/TransactionType.kt b/src/main/kotlin/moe/nea/ledger/TransactionType.kt index f6bbe6a..00feebb 100644 --- a/src/main/kotlin/moe/nea/ledger/TransactionType.kt +++ b/src/main/kotlin/moe/nea/ledger/TransactionType.kt @@ -13,11 +13,13 @@ enum class TransactionType { BAZAAR_SELL_ORDER, BITS_PURSE_STATUS, BOOSTER_COOKIE_ATE, + CAPSAICIN_EYEDROPS_USED, COMMUNITY_SHOP_BUY, CORPSE_DESECRATED, - FORGED, DIE_ROLLED, + DRACONIC_SACRIFICE, DUNGEON_CHEST_OPEN, + FORGED, KAT_TIMESKIP, KAT_UPGRADE, KISMET_REROLL, @@ -25,4 +27,5 @@ enum class TransactionType { NPC_BUY, NPC_SELL, VISITOR_BARGAIN, + WYRM_EVOKED, }
\ No newline at end of file diff --git a/src/main/kotlin/moe/nea/ledger/TriggerCommand.kt b/src/main/kotlin/moe/nea/ledger/TriggerCommand.kt new file mode 100644 index 0000000..c97627d --- /dev/null +++ b/src/main/kotlin/moe/nea/ledger/TriggerCommand.kt @@ -0,0 +1,34 @@ +package moe.nea.ledger + +import moe.nea.ledger.events.TriggerEvent +import net.minecraft.command.CommandBase +import net.minecraft.command.ICommandSender +import net.minecraft.event.ClickEvent +import net.minecraft.util.ChatComponentText +import net.minecraftforge.common.MinecraftForge + +class TriggerCommand : CommandBase() { + fun getTriggerCommandLine(trigger: String): ClickEvent { + return ClickEvent(ClickEvent.Action.RUN_COMMAND, "/${commandName} $trigger") + } + + override fun getCommandName(): String { + return "__ledgertrigger" + } + + override fun getCommandUsage(sender: ICommandSender?): String { + return "" + } + + override fun processCommand(sender: ICommandSender, args: Array<out String>) { + val event = TriggerEvent(args.joinToString(" ")) + MinecraftForge.EVENT_BUS.post(event) + if (!event.isCanceled) + sender.addChatMessage(ChatComponentText("§cCould not find the given trigger. This is an internal command for ledger.")) + } + + override fun canCommandSenderUseCommand(sender: ICommandSender?): Boolean { + return true + } + +}
\ No newline at end of file diff --git a/src/main/kotlin/moe/nea/ledger/config/LedgerConfig.kt b/src/main/kotlin/moe/nea/ledger/config/LedgerConfig.kt index 367f1e2..91ee5c1 100644 --- a/src/main/kotlin/moe/nea/ledger/config/LedgerConfig.kt +++ b/src/main/kotlin/moe/nea/ledger/config/LedgerConfig.kt @@ -20,6 +20,10 @@ class LedgerConfig : Config() { return DescriptionRendereringBehaviour.EXPAND_PANEL } + @Category(name = "Ledger", desc = "") + @JvmField + val main = MainOptions() + @Category(name = "Synchronization", desc = "") @JvmField val synchronization = SynchronizationOptions() diff --git a/src/main/kotlin/moe/nea/ledger/config/MainOptions.kt b/src/main/kotlin/moe/nea/ledger/config/MainOptions.kt new file mode 100644 index 0000000..1efa970 --- /dev/null +++ b/src/main/kotlin/moe/nea/ledger/config/MainOptions.kt @@ -0,0 +1,27 @@ +package moe.nea.ledger.config + +import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorDropdown +import io.github.notenoughupdates.moulconfig.annotations.ConfigOption + +class MainOptions { + @ConfigOption(name = "Marker for Update UI", desc = "_") + @JvmField + @UpdateUiMarker + @Transient + var testOption = Unit + + @ConfigOption(name = "Check for Updates", desc = "Automatically check for updates on startup") + @ConfigEditorDropdown + @JvmField + var updateCheck = UpdateCheckBehaviour.SEMI + + enum class UpdateCheckBehaviour(val label: String) { + SEMI("Semi-Automatic"), + FULL("Full-Automatic"), + NONE("Don't check"); + + override fun toString(): String { + return label + } + } +} diff --git a/src/main/kotlin/moe/nea/ledger/config/UpdateUi.kt b/src/main/kotlin/moe/nea/ledger/config/UpdateUi.kt new file mode 100644 index 0000000..86ccbf7 --- /dev/null +++ b/src/main/kotlin/moe/nea/ledger/config/UpdateUi.kt @@ -0,0 +1,17 @@ +package moe.nea.ledger.config + +import io.github.notenoughupdates.moulconfig.gui.GuiComponent +import io.github.notenoughupdates.moulconfig.gui.component.TextComponent +import io.github.notenoughupdates.moulconfig.gui.editors.ComponentEditor +import io.github.notenoughupdates.moulconfig.processor.ProcessedOption +import moe.nea.ledger.Ledger + +class UpdateUi(option: ProcessedOption) : ComponentEditor(option) { + val delegate by lazy {// TODO + TextComponent("Hier könnte ihre werbung stehen") + } + + override fun getDelegate(): GuiComponent { + return delegate + } +}
\ No newline at end of file diff --git a/src/main/kotlin/moe/nea/ledger/config/UpdateUiMarker.kt b/src/main/kotlin/moe/nea/ledger/config/UpdateUiMarker.kt new file mode 100644 index 0000000..7a0466a --- /dev/null +++ b/src/main/kotlin/moe/nea/ledger/config/UpdateUiMarker.kt @@ -0,0 +1,6 @@ +package moe.nea.ledger.config + +@Retention(AnnotationRetention.RUNTIME) +@Target(AnnotationTarget.FIELD) +annotation class UpdateUiMarker { +}
\ No newline at end of file diff --git a/src/main/kotlin/moe/nea/ledger/events/TriggerEvent.kt b/src/main/kotlin/moe/nea/ledger/events/TriggerEvent.kt new file mode 100644 index 0000000..3751f43 --- /dev/null +++ b/src/main/kotlin/moe/nea/ledger/events/TriggerEvent.kt @@ -0,0 +1,7 @@ +package moe.nea.ledger.events + +import net.minecraftforge.fml.common.eventhandler.Cancelable +import net.minecraftforge.fml.common.eventhandler.Event + +@Cancelable +data class TriggerEvent(val action: String) : Event() diff --git a/src/main/kotlin/moe/nea/ledger/events/WorldSwitchEvent.kt b/src/main/kotlin/moe/nea/ledger/events/WorldSwitchEvent.kt new file mode 100644 index 0000000..22a97f7 --- /dev/null +++ b/src/main/kotlin/moe/nea/ledger/events/WorldSwitchEvent.kt @@ -0,0 +1,6 @@ +package moe.nea.ledger.events + +import net.minecraftforge.fml.common.eventhandler.Event + +class WorldSwitchEvent : Event() { +}
\ No newline at end of file diff --git a/src/main/kotlin/moe/nea/ledger/modules/DragonEyePlacementDetection.kt b/src/main/kotlin/moe/nea/ledger/modules/DragonEyePlacementDetection.kt new file mode 100644 index 0000000..b9f70c4 --- /dev/null +++ b/src/main/kotlin/moe/nea/ledger/modules/DragonEyePlacementDetection.kt @@ -0,0 +1,46 @@ +package moe.nea.ledger.modules + +import moe.nea.ledger.ItemChange +import moe.nea.ledger.ItemId +import moe.nea.ledger.LedgerEntry +import moe.nea.ledger.LedgerLogger +import moe.nea.ledger.TransactionType +import moe.nea.ledger.events.ChatReceived +import moe.nea.ledger.events.WorldSwitchEvent +import moe.nea.ledger.useMatcher +import moe.nea.ledger.utils.di.Inject +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +class DragonEyePlacementDetection { + val eyePlaced = "☬ You placed a Summoning Eye!( Brace yourselves!)? \\(./.\\)".toPattern() +//☬ You placed a Summoning Eye! Brace yourselves! (8/8) + var eyeCount = 0 + + @SubscribeEvent + fun onWorldSwap(event: WorldSwitchEvent) { + eyeCount = 0 + } + + @SubscribeEvent + fun onRetrieveEye(event: ChatReceived) { + if (event.message == "You recovered a Summoning Eye!") { + eyeCount-- + } + eyePlaced.useMatcher(event.message) { + eyeCount++ + } + if (event.message == "Your Sleeping Eyes have been awoken by the magic of the Dragon. They are now Remnants of the Eye!") { + logger.logEntry(LedgerEntry( + TransactionType.WYRM_EVOKED, + event.timestamp, + listOf( + ItemChange.lose(ItemId.SUMMONING_EYE, eyeCount) + ) + )) + eyeCount = 0 + } + } + + @Inject + lateinit var logger: LedgerLogger +}
\ No newline at end of file diff --git a/src/main/kotlin/moe/nea/ledger/modules/DragonSacrificeDetection.kt b/src/main/kotlin/moe/nea/ledger/modules/DragonSacrificeDetection.kt new file mode 100644 index 0000000..20934d2 --- /dev/null +++ b/src/main/kotlin/moe/nea/ledger/modules/DragonSacrificeDetection.kt @@ -0,0 +1,71 @@ +package moe.nea.ledger.modules + +import moe.nea.ledger.DebouncedValue +import moe.nea.ledger.ItemChange +import moe.nea.ledger.ItemId +import moe.nea.ledger.ItemIdProvider +import moe.nea.ledger.LedgerEntry +import moe.nea.ledger.LedgerLogger +import moe.nea.ledger.SHORT_NUMBER_PATTERN +import moe.nea.ledger.TransactionType +import moe.nea.ledger.events.ChatReceived +import moe.nea.ledger.parseShortNumber +import moe.nea.ledger.useMatcher +import moe.nea.ledger.utils.di.Inject +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import net.minecraftforge.fml.common.gameevent.TickEvent +import kotlin.time.Duration.Companion.seconds + +class DragonSacrificeDetection { + //SACRIFICE! You turned Holy Dragon Boots into 30 Dragon Essence! + //BONUS LOOT! You also received 17x Holy Dragon Fragment from your sacrifice! + @Inject + lateinit var itemIdProvider: ItemIdProvider + + @Inject + lateinit var logger: LedgerLogger + + val sacrificePattern = + "SACRIFICE! You turned (?<sacrifice>.*) into (?<amount>$SHORT_NUMBER_PATTERN) Dragon Essence!".toPattern() + val bonusLootPattern = "BONUS LOOT! You also received (?<bonus>.*) from your sacrifice!".toPattern() + + var lastSacrifice: DebouncedValue<LedgerEntry> = DebouncedValue.farFuture() + + + @SubscribeEvent + fun onChat(event: ChatReceived) { + sacrificePattern.useMatcher(event.message) { + val sacrifice = itemIdProvider.findForName(group("sacrifice")) ?: return + val lootEssence = parseShortNumber(group("amount")) + consume(lastSacrifice.replace()) + lastSacrifice = DebouncedValue(LedgerEntry( + TransactionType.DRACONIC_SACRIFICE, + event.timestamp, + listOf( + ItemChange.lose(sacrifice, 1), + ItemChange.gain(ItemId.DRAGON_ESSENCE, lootEssence) + ) + )) + } + bonusLootPattern.useMatcher(event.message) { + val bonusItem = itemIdProvider.findStackableItemByName( + group("bonus"), true + ) ?: return + lastSacrifice.replace()?.let { + consume( + it.copy(items = it.items + ItemChange.unpairGain(bonusItem)) + ) + } + } + } + + @SubscribeEvent + fun onTick(event: TickEvent) { + consume(lastSacrifice.consume(4.seconds)) + } + + fun consume(entry: LedgerEntry?) { + if (entry != null) + logger.logEntry(entry) + } +}
\ No newline at end of file diff --git a/src/main/kotlin/moe/nea/ledger/modules/EyedropsDetection.kt b/src/main/kotlin/moe/nea/ledger/modules/EyedropsDetection.kt new file mode 100644 index 0000000..2b1a8cd --- /dev/null +++ b/src/main/kotlin/moe/nea/ledger/modules/EyedropsDetection.kt @@ -0,0 +1,34 @@ +package moe.nea.ledger.modules + +import moe.nea.ledger.ItemChange +import moe.nea.ledger.ItemId +import moe.nea.ledger.LedgerEntry +import moe.nea.ledger.LedgerLogger +import moe.nea.ledger.TransactionType +import moe.nea.ledger.events.ChatReceived +import moe.nea.ledger.useMatcher +import moe.nea.ledger.utils.di.Inject +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +class EyedropsDetection { + + val capsaicinEyedropsUsed = "You applied the eyedrops on the minion and ran out!".toPattern() + + @Inject + lateinit var logger: LedgerLogger + + @SubscribeEvent + fun onChat(event: ChatReceived) { + capsaicinEyedropsUsed.useMatcher(event.message) { + logger.logEntry( + LedgerEntry( + TransactionType.CAPSAICIN_EYEDROPS_USED, + event.timestamp, + listOf( + ItemChange.lose(ItemId.CAP_EYEDROPS, 1) + ) + ) + ) + } + } +}
\ No newline at end of file diff --git a/src/main/kotlin/moe/nea/ledger/modules/NpcDetection.kt b/src/main/kotlin/moe/nea/ledger/modules/NpcDetection.kt index c17cdc8..95b8aa5 100644 --- a/src/main/kotlin/moe/nea/ledger/modules/NpcDetection.kt +++ b/src/main/kotlin/moe/nea/ledger/modules/NpcDetection.kt @@ -10,6 +10,7 @@ import moe.nea.ledger.TransactionType import moe.nea.ledger.asIterable import moe.nea.ledger.events.BeforeGuiAction import moe.nea.ledger.events.ChatReceived +import moe.nea.ledger.events.ExtraSupplyIdEvent import moe.nea.ledger.getDisplayNameU import moe.nea.ledger.getInternalId import moe.nea.ledger.getLore @@ -46,6 +47,11 @@ class NpcDetection @Inject constructor(val ledger: LedgerLogger, val ids: ItemId } } + @SubscribeEvent + fun addChocolate(event: ExtraSupplyIdEvent) { + event.store("Chocolate", ItemId("SKYBLOCK_CHOCOLATE")) + } + @Inject lateinit var errorUtil: ErrorUtil diff --git a/src/main/kotlin/moe/nea/ledger/modules/UpdateChecker.kt b/src/main/kotlin/moe/nea/ledger/modules/UpdateChecker.kt new file mode 100644 index 0000000..0d89ca1 --- /dev/null +++ b/src/main/kotlin/moe/nea/ledger/modules/UpdateChecker.kt @@ -0,0 +1,167 @@ +package moe.nea.ledger.modules + +import com.google.gson.JsonElement +import com.google.gson.JsonPrimitive +import moe.nea.ledger.DevUtil +import moe.nea.ledger.LedgerLogger +import moe.nea.ledger.TriggerCommand +import moe.nea.ledger.config.LedgerConfig +import moe.nea.ledger.config.MainOptions +import moe.nea.ledger.events.RegistrationFinishedEvent +import moe.nea.ledger.events.TriggerEvent +import moe.nea.ledger.gen.BuildConfig +import moe.nea.ledger.utils.ErrorUtil +import moe.nea.ledger.utils.MinecraftExecutor +import moe.nea.ledger.utils.di.Inject +import moe.nea.ledger.utils.network.RequestUtil +import moe.nea.libautoupdate.CurrentVersion +import moe.nea.libautoupdate.GithubReleaseUpdateData +import moe.nea.libautoupdate.GithubReleaseUpdateSource +import moe.nea.libautoupdate.PotentialUpdate +import moe.nea.libautoupdate.UpdateContext +import moe.nea.libautoupdate.UpdateData +import moe.nea.libautoupdate.UpdateTarget +import moe.nea.libautoupdate.UpdateUtils +import net.minecraft.util.ChatComponentText +import net.minecraft.util.ChatStyle +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import java.util.concurrent.CompletableFuture + +class UpdateChecker @Inject constructor( + val errorUtil: ErrorUtil, + val requestUtil: RequestUtil, +) { + + @Inject + lateinit var minecraftExecutor: MinecraftExecutor + + val updater = UpdateContext( + NightlyAwareGithubUpdateSource("nea89o", "LocalTransactionLedger"), + if (DevUtil.isDevEnv) UpdateTarget { listOf() } + else UpdateTarget.deleteAndSaveInTheSameFolder(UpdateChecker::class.java), + object : CurrentVersion { + override fun display(): String { + return BuildConfig.VERSION + } + + override fun isOlderThan(element: JsonElement?): Boolean { + if (element !is JsonPrimitive || !element.isString) return true + val newHash = element.asString // TODO: change once i support non nightly update streams + val length = minOf(newHash.length, BuildConfig.GIT_COMMIT.length) + if (newHash.substring(0, length).equals(BuildConfig.GIT_COMMIT.substring(0, length), ignoreCase = true)) + return false + return true + } + + + override fun toString(): String { + return "{gitversion:${BuildConfig.GIT_COMMIT}, version:${BuildConfig.FULL_VERSION}}" + } + }, + "ledger" + ) + + class NightlyAwareGithubUpdateSource(owner: String, repository: String) : + GithubReleaseUpdateSource(owner, repository) { + override fun selectUpdate(updateStream: String, releases: List<GithubRelease>): UpdateData? { + if (updateStream == "nightly") { + return findAsset(releases.find { it.tagName == "nightly" }) + } + return super.selectUpdate(updateStream, releases.filter { it.tagName != "nightly" }) + } + + val releaseRegex = "commit: `(?<hash>[a-f0-9]+)`".toPattern() + + override fun findAsset(release: GithubRelease?): UpdateData? { + val update = super.findAsset(release) as GithubReleaseUpdateData? ?: return null + return GithubReleaseUpdateData( + update.versionName, + releaseRegex.matcher(update.releaseDescription) + .takeIf { it.find() } + ?.run { group("hash") } + ?.let(::JsonPrimitive) + ?: update.versionNumber, + update.sha256, + update.download, + update.releaseDescription, + update.targetCommittish, + update.createdAt, + update.publishedAt, + update.htmlUrl + ) + } + } + + init { + UpdateUtils.patchConnection { + this.requestUtil.enhanceConnection(it) + } + } + + var latestUpdate: PotentialUpdate? = null + var hasNotified = false + + @SubscribeEvent + fun onStartup(event: RegistrationFinishedEvent) { + if (config.main.updateCheck == MainOptions.UpdateCheckBehaviour.NONE) return + launchUpdateCheck() + } + + fun launchUpdateCheck() { + errorUtil.listenToFuture( + updater.checkUpdate("nightly") + .thenAcceptAsync( + { + latestUpdate = it + informAboutUpdates(it) + }, minecraftExecutor) + ) + } + + @Inject + lateinit var config: LedgerConfig + + @Inject + lateinit var triggerCommand: TriggerCommand + + val installTrigger = "execute-download" + + @Inject + lateinit var logger: LedgerLogger + fun informAboutUpdates(potentialUpdate: PotentialUpdate) { + if (hasNotified) return + hasNotified = true + if (!potentialUpdate.isUpdateAvailable) return + logger.printOut( + ChatComponentText("§aThere is a new update for LocalTransactionLedger. Click here to automatically download and install it.") + .setChatStyle(ChatStyle().setChatClickEvent(triggerCommand.getTriggerCommandLine(installTrigger)))) + if (config.main.updateCheck == MainOptions.UpdateCheckBehaviour.FULL) { + downloadUpdate() + } + } + + var updateFuture: CompletableFuture<Void>? = null + + fun downloadUpdate() { + val l = latestUpdate ?: return + if (updateFuture != null) return + // TODO: inject into findAsset to overwrite the tag id with the commit id + logger.printOut("§aTrying to download ledger update ${l.update.versionName}") + updateFuture = + latestUpdate?.launchUpdate() + ?.thenAcceptAsync( + { + logger.printOut("§aLedger update downloaded. It will automatically apply after your next restart.") + }, minecraftExecutor) + ?.let(errorUtil::listenToFuture) + } + + @SubscribeEvent + fun onTrigger(event: TriggerEvent) { + if (event.action == installTrigger) { + event.isCanceled = true + downloadUpdate() + } + } + +}
\ No newline at end of file diff --git a/src/main/kotlin/moe/nea/ledger/utils/ErrorUtil.kt b/src/main/kotlin/moe/nea/ledger/utils/ErrorUtil.kt index 344c8bc..e0c83f9 100644 --- a/src/main/kotlin/moe/nea/ledger/utils/ErrorUtil.kt +++ b/src/main/kotlin/moe/nea/ledger/utils/ErrorUtil.kt @@ -4,6 +4,7 @@ import moe.nea.ledger.utils.di.Inject import moe.nea.ledger.utils.telemetry.ContextValue import moe.nea.ledger.utils.telemetry.EventRecorder import moe.nea.ledger.utils.telemetry.Span +import java.util.concurrent.CompletableFuture class ErrorUtil { @@ -27,6 +28,14 @@ class ErrorUtil { return getOrNull() } + fun <T : CompletableFuture<*>> listenToFuture(t: T): T { + t.handle { ignored, exception -> + if (exception != null) + report(exception, "Uncaught exception in completable future") + } + return t + } + inline fun <T> catch( vararg pairs: Pair<String, ContextValue>, crossinline function: () -> T diff --git a/src/main/kotlin/moe/nea/ledger/utils/MinecraftExecutor.kt b/src/main/kotlin/moe/nea/ledger/utils/MinecraftExecutor.kt new file mode 100644 index 0000000..affd86c --- /dev/null +++ b/src/main/kotlin/moe/nea/ledger/utils/MinecraftExecutor.kt @@ -0,0 +1,10 @@ +package moe.nea.ledger.utils + +import net.minecraft.client.Minecraft +import java.util.concurrent.Executor + +class MinecraftExecutor : Executor { + override fun execute(command: Runnable) { + Minecraft.getMinecraft().addScheduledTask(command) + } +}
\ No newline at end of file diff --git a/src/main/kotlin/moe/nea/ledger/utils/di/DI.kt b/src/main/kotlin/moe/nea/ledger/utils/di/DI.kt index 133637a..a9061d7 100644 --- a/src/main/kotlin/moe/nea/ledger/utils/di/DI.kt +++ b/src/main/kotlin/moe/nea/ledger/utils/di/DI.kt @@ -9,25 +9,32 @@ class DI { private fun formatInjectionStack() = injectionStack.joinToString(" -> ") + fun <T : Any> getProvider(type: Class<T>): BaseDIProvider<T, *> { + val provider = providers[type] as BaseDIProvider<T, *>? + ?: error("Could not find provider for type $type") + return provider + } + private fun <T : Any, C> internalProvide(type: Class<T>, element: AnnotatedElement? = null): T { - val provider = providers[type] as BaseDIProvider<T, C> - val context = if (element == null) provider.createEmptyContext() else provider.createContext(element) - val key = Pair(type, context) - val existingValue = values[key] - if (existingValue != null) return existingValue as T - if (type in injectionStack) { - error("Found injection cycle: ${formatInjectionStack()} -> $type") - } - injectionStack.push(type) - val value = try { - provider.provideWithContext(this, context) + try { + val provider = getProvider(type) as BaseDIProvider<T, C> + val context = if (element == null) provider.createEmptyContext() else provider.createContext(element) + val key = Pair(type, context) + val existingValue = values[key] + if (existingValue != null) return existingValue as T + if (type in injectionStack) { + error("Found injection cycle: ${formatInjectionStack()} -> $type") + } + injectionStack.push(type) + val value = + provider.provideWithContext(this, context) + val cycleCheckCookie = injectionStack.pop() + require(cycleCheckCookie == type) { "Unbalanced stack cookie: $cycleCheckCookie != $type" } + values[key] = value + return value } catch (ex: Exception) { throw RuntimeException("Could not create instance for type $type (in stack ${formatInjectionStack()})", ex) } - val cycleCheckCookie = injectionStack.pop() - require(cycleCheckCookie == type) { "Unbalanced stack cookie: $cycleCheckCookie != $type" } - values[key] = value - return value } fun <T : Any> provide(type: Class<T>, element: AnnotatedElement? = null): T { |