diff options
Diffstat (limited to 'src/main')
25 files changed, 399 insertions, 167 deletions
diff --git a/src/main/java/moe/nea/firmament/mixins/MixinHandledScreen.java b/src/main/java/moe/nea/firmament/mixins/MixinHandledScreen.java index e607ba3..43aec40 100644 --- a/src/main/java/moe/nea/firmament/mixins/MixinHandledScreen.java +++ b/src/main/java/moe/nea/firmament/mixins/MixinHandledScreen.java @@ -4,8 +4,11 @@ package moe.nea.firmament.mixins; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; -import com.llamalad7.mixinextras.sugar.Local; -import moe.nea.firmament.events.*; +import moe.nea.firmament.events.HandledScreenClickEvent; +import moe.nea.firmament.events.HandledScreenForegroundEvent; +import moe.nea.firmament.events.HandledScreenKeyPressedEvent; +import moe.nea.firmament.events.IsSlotProtectedEvent; +import moe.nea.firmament.events.SlotRenderEvents; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.ingame.HandledScreen; import net.minecraft.entity.player.PlayerInventory; @@ -22,9 +25,6 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; -import org.spongepowered.asm.mixin.injection.callback.LocalCapture; - -import java.util.Iterator; @Mixin(value = HandledScreen.class, priority = 990) public abstract class MixinHandledScreen<T extends ScreenHandler> { @@ -74,17 +74,17 @@ public abstract class MixinHandledScreen<T extends ScreenHandler> { public void onMouseClickedSlot(Slot slot, int slotId, int button, SlotActionType actionType, CallbackInfo ci) { if (slotId == -999 && getScreenHandler() != null && actionType == SlotActionType.PICKUP) { // -999 is code for "clicked outside the main window" ItemStack cursorStack = getScreenHandler().getCursorStack(); - if (cursorStack != null && IsSlotProtectedEvent.shouldBlockInteraction(slot, SlotActionType.THROW, cursorStack)) { + if (cursorStack != null && IsSlotProtectedEvent.shouldBlockInteraction(slot, SlotActionType.THROW, IsSlotProtectedEvent.MoveOrigin.INVENTORY_MOVE, cursorStack)) { ci.cancel(); return; } } - if (IsSlotProtectedEvent.shouldBlockInteraction(slot, actionType)) { + if (IsSlotProtectedEvent.shouldBlockInteraction(slot, actionType, IsSlotProtectedEvent.MoveOrigin.INVENTORY_MOVE)) { ci.cancel(); return; } if (actionType == SlotActionType.SWAP && 0 <= button && button < 9) { - if (IsSlotProtectedEvent.shouldBlockInteraction(new Slot(playerInventory, button, 0, 0), actionType)) { + if (IsSlotProtectedEvent.shouldBlockInteraction(new Slot(playerInventory, button, 0, 0), actionType, IsSlotProtectedEvent.MoveOrigin.INVENTORY_MOVE)) { ci.cancel(); } } diff --git a/src/main/java/moe/nea/firmament/mixins/PlayerDropEventPatch.java b/src/main/java/moe/nea/firmament/mixins/PlayerDropEventPatch.java index 9a4626f..b20c223 100644 --- a/src/main/java/moe/nea/firmament/mixins/PlayerDropEventPatch.java +++ b/src/main/java/moe/nea/firmament/mixins/PlayerDropEventPatch.java @@ -14,15 +14,15 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @Mixin(ClientPlayerEntity.class) public abstract class PlayerDropEventPatch extends PlayerEntity { - public PlayerDropEventPatch() { - super(null, null, 0, null); - } + public PlayerDropEventPatch() { + super(null, null, 0, null); + } - @Inject(method = "dropSelectedItem", at = @At("HEAD"), cancellable = true) - public void onDropSelectedItem(boolean entireStack, CallbackInfoReturnable<Boolean> cir) { - Slot fakeSlot = new Slot(getInventory(), getInventory().selectedSlot, 0, 0); - if (IsSlotProtectedEvent.shouldBlockInteraction(fakeSlot, SlotActionType.THROW)) { - cir.setReturnValue(false); - } - } + @Inject(method = "dropSelectedItem", at = @At("HEAD"), cancellable = true) + public void onDropSelectedItem(boolean entireStack, CallbackInfoReturnable<Boolean> cir) { + Slot fakeSlot = new Slot(getInventory(), getInventory().selectedSlot, 0, 0); + if (IsSlotProtectedEvent.shouldBlockInteraction(fakeSlot, SlotActionType.THROW, IsSlotProtectedEvent.MoveOrigin.DROP_FROM_HOTBAR)) { + cir.setReturnValue(false); + } + } } diff --git a/src/main/kotlin/events/CustomItemModelEvent.kt b/src/main/kotlin/events/CustomItemModelEvent.kt index 11528fd..21ee326 100644 --- a/src/main/kotlin/events/CustomItemModelEvent.kt +++ b/src/main/kotlin/events/CustomItemModelEvent.kt @@ -1,23 +1,43 @@ package moe.nea.firmament.events +import java.util.Optional +import kotlin.jvm.optionals.getOrNull import net.minecraft.item.ItemStack import net.minecraft.util.Identifier +import moe.nea.firmament.util.collections.WeakCache +import moe.nea.firmament.util.mc.IntrospectableItemModelManager // TODO: assert an order on these events data class CustomItemModelEvent( val itemStack: ItemStack, + val itemModelManager: IntrospectableItemModelManager, var overrideModel: Identifier? = null, ) : FirmamentEvent() { companion object : FirmamentEventBus<CustomItemModelEvent>() { + val cache = WeakCache.memoize("ItemModelIdentifier", ::getModelIdentifier0) + @JvmStatic - fun getModelIdentifier(itemStack: ItemStack?): Identifier? { - // TODO: Re-add memoization and add an error / warning if the model does not exist + fun getModelIdentifier(itemStack: ItemStack?, itemModelManager: IntrospectableItemModelManager): Identifier? { if (itemStack == null) return null - return publish(CustomItemModelEvent(itemStack)).overrideModel + return cache.invoke(itemStack, itemModelManager).getOrNull() + } + + fun getModelIdentifier0( + itemStack: ItemStack, + itemModelManager: IntrospectableItemModelManager + ): Optional<Identifier> { + // TODO: add an error / warning if the model does not exist + return Optional.ofNullable(publish(CustomItemModelEvent(itemStack, itemModelManager)).overrideModel) } } fun overrideIfExists(overrideModel: Identifier) { - this.overrideModel = overrideModel + if (itemModelManager.hasModel_firmament(overrideModel)) + this.overrideModel = overrideModel + } + + fun overrideIfEmpty(identifier: Identifier) { + if (overrideModel == null) + overrideModel = identifier } } diff --git a/src/main/kotlin/events/FirmamentEventBus.kt b/src/main/kotlin/events/FirmamentEventBus.kt index 71331d1..af4e16a 100644 --- a/src/main/kotlin/events/FirmamentEventBus.kt +++ b/src/main/kotlin/events/FirmamentEventBus.kt @@ -3,6 +3,7 @@ package moe.nea.firmament.events import java.util.concurrent.CopyOnWriteArrayList import org.apache.commons.lang3.reflect.TypeUtils import moe.nea.firmament.Firmament +import moe.nea.firmament.util.ErrorUtil import moe.nea.firmament.util.MC /** @@ -48,7 +49,7 @@ open class FirmamentEventBus<T : FirmamentEvent> { val klass = e.javaClass if (!function.knownErrors.contains(klass) || Firmament.DEBUG) { function.knownErrors.add(klass) - Firmament.logger.error("Caught exception during processing event $event by $function", e) + ErrorUtil.softError("Caught exception during processing event $event by $function", e) } } } diff --git a/src/main/kotlin/events/IsSlotProtectedEvent.kt b/src/main/kotlin/events/IsSlotProtectedEvent.kt index cd2b676..eac2d9b 100644 --- a/src/main/kotlin/events/IsSlotProtectedEvent.kt +++ b/src/main/kotlin/events/IsSlotProtectedEvent.kt @@ -1,5 +1,3 @@ - - package moe.nea.firmament.events import net.minecraft.item.ItemStack @@ -10,37 +8,49 @@ import moe.nea.firmament.util.CommonSoundEffects import moe.nea.firmament.util.MC data class IsSlotProtectedEvent( - val slot: Slot?, - val actionType: SlotActionType, - var isProtected: Boolean, - val itemStackOverride: ItemStack?, - var silent: Boolean = false, + val slot: Slot?, + val actionType: SlotActionType, + var isProtected: Boolean, + val itemStackOverride: ItemStack?, + val origin: MoveOrigin, + var silent: Boolean = false, ) : FirmamentEvent() { - val itemStack get() = itemStackOverride ?: slot!!.stack + val itemStack get() = itemStackOverride ?: slot!!.stack - fun protect() { - isProtected = true - } + fun protect() { + isProtected = true + silent = false + } - fun protectSilent() { - if (!isProtected) { - silent = true - } - isProtected = true - } + fun protectSilent() { + if (!isProtected) { + silent = true + } + isProtected = true + } - companion object : FirmamentEventBus<IsSlotProtectedEvent>() { - @JvmStatic - @JvmOverloads - fun shouldBlockInteraction(slot: Slot?, action: SlotActionType, itemStackOverride: ItemStack? = null): Boolean { - if (slot == null && itemStackOverride == null) return false - val event = IsSlotProtectedEvent(slot, action, false, itemStackOverride) - publish(event) - if (event.isProtected && !event.silent) { - MC.sendChat(Text.translatable("firmament.protectitem").append(event.itemStack.name)) - CommonSoundEffects.playFailure() - } - return event.isProtected - } - } + enum class MoveOrigin { + DROP_FROM_HOTBAR, + SALVAGE, + INVENTORY_MOVE + ; + } + companion object : FirmamentEventBus<IsSlotProtectedEvent>() { + @JvmStatic + @JvmOverloads + fun shouldBlockInteraction( + slot: Slot?, action: SlotActionType, + origin: MoveOrigin, + itemStackOverride: ItemStack? = null, + ): Boolean { + if (slot == null && itemStackOverride == null) return false + val event = IsSlotProtectedEvent(slot, action, false, itemStackOverride, origin) + publish(event) + if (event.isProtected && !event.silent) { + MC.sendChat(Text.translatable("firmament.protectitem").append(event.itemStack.name)) + CommonSoundEffects.playFailure() + } + return event.isProtected + } + } } diff --git a/src/main/kotlin/features/FeatureManager.kt b/src/main/kotlin/features/FeatureManager.kt index 0f5ebf8..9a3cbf8 100644 --- a/src/main/kotlin/features/FeatureManager.kt +++ b/src/main/kotlin/features/FeatureManager.kt @@ -45,10 +45,6 @@ object FeatureManager : DataHolder<FeatureManager.Config>(serializer(), "feature private var hasAutoloaded = false - init { - autoload() - } - fun autoload() { synchronized(this) { if (hasAutoloaded) return diff --git a/src/main/kotlin/features/debug/PowerUserTools.kt b/src/main/kotlin/features/debug/PowerUserTools.kt index 225bc13..ac7a6fb 100644 --- a/src/main/kotlin/features/debug/PowerUserTools.kt +++ b/src/main/kotlin/features/debug/PowerUserTools.kt @@ -30,6 +30,7 @@ import moe.nea.firmament.mixins.accessor.AccessorHandledScreen import moe.nea.firmament.util.ClipboardUtils import moe.nea.firmament.util.MC import moe.nea.firmament.util.focusedItemStack +import moe.nea.firmament.util.mc.IntrospectableItemModelManager import moe.nea.firmament.util.mc.SNbtFormatter.Companion.toPrettyString import moe.nea.firmament.util.mc.displayNameAccordingToNbt import moe.nea.firmament.util.mc.loreAccordingToNbt @@ -119,7 +120,11 @@ object PowerUserTools : FirmamentFeature { lastCopiedStack = Pair(item, Text.stringifiedTranslatable("firmament.tooltip.copied.skyblockid", sbId.neuItem)) } else if (it.matches(TConfig.copyTexturePackId)) { - val model = CustomItemModelEvent.getModelIdentifier(item) // TODO: remove global texture overrides, maybe + val model = CustomItemModelEvent.getModelIdentifier0(item, object : IntrospectableItemModelManager { + override fun hasModel_firmament(identifier: Identifier): Boolean { + return true + } + }).getOrNull() // TODO: remove global texture overrides, maybe if (model == null) { lastCopiedStack = Pair(item, Text.translatable("firmament.tooltip.copied.modelid.fail")) return diff --git a/src/main/kotlin/features/inventory/SlotLocking.kt b/src/main/kotlin/features/inventory/SlotLocking.kt index 99130d5..7a3a152 100644 --- a/src/main/kotlin/features/inventory/SlotLocking.kt +++ b/src/main/kotlin/features/inventory/SlotLocking.kt @@ -15,6 +15,7 @@ import net.minecraft.screen.slot.SlotActionType import net.minecraft.util.Identifier import net.minecraft.util.StringIdentifiable import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.events.FeaturesInitializedEvent import moe.nea.firmament.events.HandledScreenForegroundEvent import moe.nea.firmament.events.HandledScreenKeyPressedEvent import moe.nea.firmament.events.HandledScreenKeyReleasedEvent @@ -37,6 +38,7 @@ import moe.nea.firmament.util.mc.displayNameAccordingToNbt import moe.nea.firmament.util.mc.loreAccordingToNbt import moe.nea.firmament.util.render.GuiRenderLayers import moe.nea.firmament.util.render.drawLine +import moe.nea.firmament.util.skyblock.DungeonUtil import moe.nea.firmament.util.skyblockUUID import moe.nea.firmament.util.unformattedString @@ -60,6 +62,7 @@ object SlotLocking : FirmamentFeature { val slotBind by keyBinding("bind") { GLFW.GLFW_KEY_L } val slotBindRequireShift by toggle("require-quick-move") { true } val slotRenderLines by choice("bind-render") { SlotRenderLinesMode.ONLY_BOXES } + val allowDroppingInDungeons by toggle("drop-in-dungeons") { true } } enum class SlotRenderLinesMode : StringIdentifiable { @@ -120,7 +123,11 @@ object SlotLocking : FirmamentFeature { var anyBlocked = false for (i in 0 until event.slot.index) { val stack = inv.getStack(i) - if (IsSlotProtectedEvent.shouldBlockInteraction(null, SlotActionType.THROW, stack)) + if (IsSlotProtectedEvent.shouldBlockInteraction(null, + SlotActionType.THROW, + IsSlotProtectedEvent.MoveOrigin.SALVAGE, + stack) + ) anyBlocked = true } if (anyBlocked) { @@ -156,6 +163,19 @@ object SlotLocking : FirmamentFeature { } @Subscribe + fun onEvent(event: FeaturesInitializedEvent) { + IsSlotProtectedEvent.subscribe(receivesCancelled = true, "SlotLocking:unlockInDungeons") { + if (it.isProtected + && it.origin == IsSlotProtectedEvent.MoveOrigin.DROP_FROM_HOTBAR + && DungeonUtil.isInActiveDungeon + && TConfig.allowDroppingInDungeons + ) { + it.isProtected = false + } + } + } + + @Subscribe fun onQuickMoveBoundSlot(it: IsSlotProtectedEvent) { val boundSlots = DConfig.data?.boundSlots ?: mapOf() val isValidAction = @@ -245,7 +265,7 @@ object SlotLocking : FirmamentFeature { val (hotX, hotY) = hotbarSlot.lineCenter() val (invX, invY) = inventorySlot.lineCenter() val anyHovered = accScreen.focusedSlot_Firmament === hotbarSlot - || accScreen.focusedSlot_Firmament === inventorySlot + || accScreen.focusedSlot_Firmament === inventorySlot if (!anyHovered && TConfig.slotRenderLines == SlotRenderLinesMode.NOTHING) continue val color = if (anyHovered) diff --git a/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayScreen.kt b/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayScreen.kt index cf1cf1d..633a8fe 100644 --- a/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayScreen.kt +++ b/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayScreen.kt @@ -35,6 +35,7 @@ import moe.nea.firmament.util.mc.FakeSlot import moe.nea.firmament.util.mc.displayNameAccordingToNbt import moe.nea.firmament.util.mc.loreAccordingToNbt import moe.nea.firmament.util.render.drawGuiTexture +import moe.nea.firmament.util.render.enableScissorWithoutTranslation import moe.nea.firmament.util.tr import moe.nea.firmament.util.unformattedString @@ -241,9 +242,9 @@ class StorageOverlayScreen : Screen(Text.literal("")) { fun createScissors(context: DrawContext) { val rect = getScrollPanelInner() - context.enableScissor( - rect.minX, rect.minY, - rect.maxX, rect.maxY + context.enableScissorWithoutTranslation( + rect.minX.toFloat(), rect.minY.toFloat(), + rect.maxX.toFloat(), rect.maxY.toFloat(), ) } @@ -302,6 +303,7 @@ class StorageOverlayScreen : Screen(Text.literal("")) { } fun mouseClicked(mouseX: Double, mouseY: Double, button: Int, activePage: StoragePageSlot?): Boolean { + guiContext.setFocusedElement(null) // Blur all elements. They will be refocused by clickMCComponentInPlace if in doubt, and we don't have any double click components. if (getScrollPanelInner().contains(mouseX, mouseY)) { val data = StorageOverlay.Data.data ?: StorageData() layoutedForEach(data) { rect, page, _ -> diff --git a/src/main/kotlin/features/mining/PickaxeAbility.kt b/src/main/kotlin/features/mining/PickaxeAbility.kt index 94b49f9..1737969 100644 --- a/src/main/kotlin/features/mining/PickaxeAbility.kt +++ b/src/main/kotlin/features/mining/PickaxeAbility.kt @@ -156,10 +156,21 @@ object PickaxeAbility : FirmamentFeature { fun onChatMessage(it: ProcessChatEvent) { abilityUsePattern.useMatch(it.unformattedString) { lastUsage[group("name")] = TimeMark.now() + abilityOverride = group("name") } abilitySwitchPattern.useMatch(it.unformattedString) { abilityOverride = group("ability") } + pickaxeAbilityCooldownPattern.useMatch(it.unformattedString) { + val ability = abilityOverride ?: return@useMatch + val remainingCooldown = parseTimePattern(group("remainingCooldown")) + val length = defaultAbilityDurations[ability] ?: return@useMatch + lastUsage[ability] = TimeMark.ago(length - remainingCooldown) + } + nowAvailable.useMatch(it.unformattedString) { + val ability = group("name") + lastUsage[ability] = TimeMark.farPast() + } } @Subscribe @@ -179,6 +190,7 @@ object PickaxeAbility : FirmamentFeature { val fuelPattern = Pattern.compile("Fuel: .*/(?<maxFuel>$SHORT_NUMBER_FORMAT)") val pickaxeAbilityCooldownPattern = Pattern.compile("Your pickaxe ability is on cooldown for (?<remainingCooldown>$TIME_PATTERN)\\.") + val nowAvailable = Pattern.compile("(?<name>[a-zA-Z0-9 ]+) is now available!") data class PickaxeAbilityData( val name: String, diff --git a/src/main/kotlin/features/world/Waypoints.kt b/src/main/kotlin/features/world/Waypoints.kt index 16db059..3ebfe70 100644 --- a/src/main/kotlin/features/world/Waypoints.kt +++ b/src/main/kotlin/features/world/Waypoints.kt @@ -4,6 +4,7 @@ import com.mojang.brigadier.arguments.IntegerArgumentType import me.shedaniel.math.Color import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource import kotlinx.serialization.Serializable +import kotlinx.serialization.encodeToString import kotlin.collections.component1 import kotlin.collections.component2 import kotlin.collections.set @@ -32,6 +33,7 @@ import moe.nea.firmament.util.ClipboardUtils import moe.nea.firmament.util.MC import moe.nea.firmament.util.TimeMark import moe.nea.firmament.util.render.RenderInWorldContext +import moe.nea.firmament.util.tr object Waypoints : FirmamentFeature { override val identifier: String @@ -198,11 +200,22 @@ object Waypoints : FirmamentFeature { } } } + thenLiteral("export") { + thenExecute { + val data = Firmament.tightJson.encodeToString<List<ColeWeightWaypoint>>(waypoints.map { + ColeWeightWaypoint(it.x, + it.y, + it.z) + }) + ClipboardUtils.setTextContent(data) + source.sendFeedback(tr("firmament.command.waypoint.export", "Copied ${waypoints.size} waypoints to clipboard")) + } + } thenLiteral("import") { thenExecute { val contents = ClipboardUtils.getTextContents() val data = try { - Firmament.json.decodeFromString<List<ColeWeightWaypoint>>(contents) + Firmament.tightJson.decodeFromString<List<ColeWeightWaypoint>>(contents) } catch (ex: Exception) { Firmament.logger.error("Could not load waypoints from clipboard", ex) source.sendError(Text.translatable("firmament.command.waypoint.import.error")) diff --git a/src/main/kotlin/repo/BetterRepoRecipeCache.kt b/src/main/kotlin/repo/BetterRepoRecipeCache.kt index 4b32e57..6d18223 100644 --- a/src/main/kotlin/repo/BetterRepoRecipeCache.kt +++ b/src/main/kotlin/repo/BetterRepoRecipeCache.kt @@ -2,8 +2,10 @@ package moe.nea.firmament.repo import io.github.moulberry.repo.IReloadable import io.github.moulberry.repo.NEURepository +import io.github.moulberry.repo.data.NEUNpcShopRecipe import io.github.moulberry.repo.data.NEURecipe import moe.nea.firmament.util.SkyblockId +import moe.nea.firmament.util.skyblockId class BetterRepoRecipeCache(vararg val extraProviders: ExtraRecipeProvider) : IReloadable { var usages: Map<SkyblockId, Set<NEURecipe>> = mapOf() @@ -17,6 +19,9 @@ class BetterRepoRecipeCache(vararg val extraProviders: ExtraRecipeProvider) : IR .flatMap { it.recipes } (baseRecipes + extraProviders.flatMap { it.provideExtraRecipes() }) .forEach { recipe -> + if (recipe is NEUNpcShopRecipe) { + usages.getOrPut(recipe.isSoldBy.skyblockId, ::mutableSetOf).add(recipe) + } recipe.allInputs.forEach { usages.getOrPut(SkyblockId(it.itemId), ::mutableSetOf).add(recipe) } recipe.allOutputs.forEach { recipes.getOrPut(SkyblockId(it.itemId), ::mutableSetOf).add(recipe) } } diff --git a/src/main/kotlin/repo/ItemCache.kt b/src/main/kotlin/repo/ItemCache.kt index e140dd8..9fa0083 100644 --- a/src/main/kotlin/repo/ItemCache.kt +++ b/src/main/kotlin/repo/ItemCache.kt @@ -24,6 +24,7 @@ import net.minecraft.nbt.NbtCompound import net.minecraft.nbt.NbtElement import net.minecraft.nbt.NbtOps import net.minecraft.nbt.NbtString +import net.minecraft.text.MutableText import net.minecraft.text.Style import net.minecraft.text.Text import moe.nea.firmament.Firmament @@ -100,7 +101,7 @@ object ItemCache : IReloadable { } } - fun un189Lore(lore: String): Text { + fun un189Lore(lore: String): MutableText { val base = Text.literal("") base.setStyle(Style.EMPTY.withItalic(false)) var lastColorCode = Style.EMPTY diff --git a/src/main/kotlin/repo/SBItemStack.kt b/src/main/kotlin/repo/SBItemStack.kt index 4d07801..da34707 100644 --- a/src/main/kotlin/repo/SBItemStack.kt +++ b/src/main/kotlin/repo/SBItemStack.kt @@ -17,8 +17,10 @@ import moe.nea.firmament.repo.ItemCache.asItemStack import moe.nea.firmament.repo.ItemCache.withFallback import moe.nea.firmament.util.FirmFormatters import moe.nea.firmament.util.LegacyFormattingCode +import moe.nea.firmament.util.MC import moe.nea.firmament.util.ReforgeId import moe.nea.firmament.util.SkyblockId +import moe.nea.firmament.util.blue import moe.nea.firmament.util.directLiteralStringContent import moe.nea.firmament.util.extraAttributes import moe.nea.firmament.util.getReforgeId @@ -27,12 +29,16 @@ import moe.nea.firmament.util.grey import moe.nea.firmament.util.mc.appendLore import moe.nea.firmament.util.mc.displayNameAccordingToNbt import moe.nea.firmament.util.mc.loreAccordingToNbt +import moe.nea.firmament.util.mc.modifyLore +import moe.nea.firmament.util.modifyExtraAttributes import moe.nea.firmament.util.petData import moe.nea.firmament.util.prepend +import moe.nea.firmament.util.reconstitute import moe.nea.firmament.util.skyBlockId import moe.nea.firmament.util.skyblock.ItemType import moe.nea.firmament.util.skyblock.Rarity import moe.nea.firmament.util.skyblockId +import moe.nea.firmament.util.unformattedString import moe.nea.firmament.util.useMatch import moe.nea.firmament.util.withColor @@ -99,6 +105,13 @@ data class SBItemStack constructor( return SBItemStack(SkyblockId.NULL, null, itemStack.count, null, fallback = itemStack) } + fun parseStatBlock(itemStack: ItemStack): List<StatLine> { + return itemStack.loreAccordingToNbt + .map { parseStatLine(it) } + .takeWhile { it != null } + .filterNotNull() + } + fun appendEnhancedStats( itemStack: ItemStack, reforgeStats: Map<String, Double>, @@ -135,6 +148,7 @@ data class SBItemStack constructor( data class StatFormatting( val postFix: String, val color: Formatting, + val isStarAffected: Boolean = true, ) val formattingOverrides = mapOf( @@ -142,7 +156,7 @@ data class SBItemStack constructor( "Strength" to StatFormatting("", Formatting.RED), "Damage" to StatFormatting("", Formatting.RED), "Bonus Attack Speed" to StatFormatting("%", Formatting.RED), - "Shot Cooldown" to StatFormatting("s", Formatting.RED), + "Shot Cooldown" to StatFormatting("s", Formatting.GREEN, false), "Ability Damage" to StatFormatting("%", Formatting.RED), "Crit Damage" to StatFormatting("%", Formatting.RED), "Crit Chance" to StatFormatting("%", Formatting.RED), @@ -184,9 +198,11 @@ data class SBItemStack constructor( val color: Formatting, val prefix: String, val postFix: String, + val isHidden: Boolean, ) { - REFORGE(Formatting.BLUE, "(", ")"), - + REFORGE(Formatting.BLUE, "(", ")", false), + STAR_BUFF(Formatting.RESET, "", "", true), + CATA_STAR_BUFF(Formatting.DARK_GRAY, "(", ")", false), ; } @@ -194,7 +210,7 @@ data class SBItemStack constructor( val statName: String, val value: Text?, val rest: List<Text> = listOf(), - val valueNum: Double? = value?.directLiteralStringContent?.trim(' ', '%', '+')?.toDoubleOrNull() + val valueNum: Double? = value?.directLiteralStringContent?.trim(' ', 's', '%', '+')?.toDoubleOrNull() ) { fun addStat(amount: Double, buffKind: BuffKind): StatLine { val formattedAmount = FirmFormatters.formatCommas(amount, 1, includeSign = true) @@ -202,7 +218,8 @@ data class SBItemStack constructor( valueNum = (valueNum ?: 0.0) + amount, value = null, rest = rest + - listOf( + if (buffKind.isHidden) emptyList() + else listOf( Text.literal( buffKind.prefix + formattedAmount + statFormatting.postFix + @@ -308,6 +325,22 @@ data class SBItemStack constructor( data.putString("modifier", reforgeId.id) itemStack.extraAttributes = data appendEnhancedStats(itemStack, reforgeStats, BuffKind.REFORGE) + reforge.reforgeAbility?.get(rarity)?.let { reforgeAbility -> + val formattedReforgeAbility = ItemCache.un189Lore(reforgeAbility) + .grey() + itemStack.modifyLore { + val lastBlank = it.indexOfLast { it.unformattedString.isBlank() } + val newList = mutableListOf<Text>() + newList.addAll(it.subList(0, lastBlank)) + newList.add(Text.literal("")) + newList.add(Text.literal("${reforge.reforgeName} Bonus").blue()) + MC.font.textHandler.wrapLines(formattedReforgeAbility, 180, Style.EMPTY).mapTo(newList) { + it.reconstitute() + } + newList.addAll(it.subList(lastBlank, it.size)) + return@modifyLore newList + } + } } // TODO: avoid instantiating the item stack here @@ -325,12 +358,14 @@ data class SBItemStack constructor( return@run ItemStack.EMPTY val replacementData = mutableMapOf<String, String>() injectReplacementDataForPets(replacementData) - return@run neuItem.asItemStack(idHint = skyblockId, replacementData) + val baseItem = neuItem.asItemStack(idHint = skyblockId, replacementData) .withFallback(fallback) .copyWithCount(stackSize) - .also { appendReforgeInfo(it) } - .also { it.appendLore(extraLore) } - .also { enhanceStatsByStars(it, stars) } + val baseStats = parseStatBlock(baseItem) + appendReforgeInfo(baseItem) + baseItem.appendLore(extraLore) + enhanceStatsByStars(baseItem, stars, baseStats) + return@run baseItem } if (itemStack_ == null) itemStack_ = itemStack @@ -340,6 +375,7 @@ data class SBItemStack constructor( private fun starString(stars: Int): Text { if (stars <= 0) return Text.empty() + // TODO: idk master stars val tiers = listOf( LegacyFormattingCode.GOLD, LegacyFormattingCode.LIGHT_PURPLE, @@ -359,11 +395,23 @@ data class SBItemStack constructor( return starString } - private fun enhanceStatsByStars(itemStack: ItemStack, stars: Int) { + private fun enhanceStatsByStars(itemStack: ItemStack, stars: Int, baseStats: List<StatLine>) { if (stars == 0) return // TODO: increase stats and add the star level into the nbt data so star displays work + itemStack.modifyExtraAttributes { + it.putInt("upgrade_level", stars) + } itemStack.displayNameAccordingToNbt = itemStack.displayNameAccordingToNbt.copy() .append(starString(stars)) + val isDungeon = ItemType.fromItemStack(itemStack)?.isDungeon ?: true + val truncatedStarCount = if (isDungeon) minOf(5, stars) else stars + appendEnhancedStats(itemStack, + baseStats + .filter { it.statFormatting.isStarAffected } + .associate { + it.statName to ((it.valueNum ?: 0.0) * (truncatedStarCount * 0.02)) + }, + BuffKind.STAR_BUFF) } fun asImmutableItemStack(): ItemStack { diff --git a/src/main/kotlin/util/FirmFormatters.kt b/src/main/kotlin/util/FirmFormatters.kt index 4b32c2a..acb7102 100644 --- a/src/main/kotlin/util/FirmFormatters.kt +++ b/src/main/kotlin/util/FirmFormatters.kt @@ -9,11 +9,39 @@ 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, includeSign: Boolean = false): String { if (long < 0 && long != Long.MIN_VALUE) { 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 e785ff6..b2f9449 100644 --- a/src/main/kotlin/util/SBData.kt +++ b/src/main/kotlin/util/SBData.kt @@ -19,6 +19,11 @@ object SBData { "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 + } /** * Source: https://hypixel-skyblock.fandom.com/wiki/Time_Systems @@ -37,11 +42,15 @@ object SBData { 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() } } 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 f15cadb..a86543c 100644 --- a/src/main/kotlin/util/SkyBlockIsland.kt +++ b/src/main/kotlin/util/SkyBlockIsland.kt @@ -36,6 +36,7 @@ private constructor( 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 631b444..a99afda 100644 --- a/src/main/kotlin/util/SkyblockId.kt +++ b/src/main/kotlin/util/SkyblockId.kt @@ -119,6 +119,12 @@ var 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() } 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/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 6c7096c..7a776b5 100644 --- a/src/main/kotlin/util/skyblock/ItemType.kt +++ b/src/main/kotlin/util/skyblock/ItemType.kt @@ -67,6 +67,8 @@ value class ItemType private constructor(val name: String) { 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/textutil.kt b/src/main/kotlin/util/textutil.kt index ab3de43..c295ae0 100644 --- a/src/main/kotlin/util/textutil.kt +++ b/src/main/kotlin/util/textutil.kt @@ -1,71 +1,17 @@ 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 { @@ -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 @@ -135,6 +122,7 @@ 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) } |