From 13994393ed392b33161cd427cc9730c9f1b11e35 Mon Sep 17 00:00:00 2001 From: Linnea Gräf Date: Sun, 22 Dec 2024 16:29:18 +0100 Subject: feat: Hide potion effects --- .../firmament/mixins/HideStatusEffectsPatch.java | 29 ++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/main/java/moe/nea/firmament/mixins/HideStatusEffectsPatch.java (limited to 'src/main/java') diff --git a/src/main/java/moe/nea/firmament/mixins/HideStatusEffectsPatch.java b/src/main/java/moe/nea/firmament/mixins/HideStatusEffectsPatch.java new file mode 100644 index 0000000..c5af8b6 --- /dev/null +++ b/src/main/java/moe/nea/firmament/mixins/HideStatusEffectsPatch.java @@ -0,0 +1,29 @@ +package moe.nea.firmament.mixins; + +import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; +import moe.nea.firmament.features.fixes.Fixes; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.ingame.InventoryScreen; +import net.minecraft.client.gui.screen.ingame.StatusEffectsDisplay; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(InventoryScreen.class) +public abstract class HideStatusEffectsPatch { + @Shadow + public abstract boolean shouldHideStatusEffectHud(); + + @Inject(method = "shouldHideStatusEffectHud", at = @At("HEAD"), cancellable = true) + private void hideStatusEffects(CallbackInfoReturnable cir) { + cir.setReturnValue(!Fixes.TConfig.INSTANCE.getHidePotionEffects()); + } + + @WrapWithCondition(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/ingame/StatusEffectsDisplay;drawStatusEffects(Lnet/minecraft/client/gui/DrawContext;IIF)V")) + private boolean conditionalRenderStatuses(StatusEffectsDisplay instance, DrawContext context, int mouseX, int mouseY, float tickDelta) { + return shouldHideStatusEffectHud() || !Fixes.TConfig.INSTANCE.getHidePotionEffects(); + } + +} -- cgit From 656958937fe29da90d7229b979996a68b5ea5b67 Mon Sep 17 00:00:00 2001 From: Linnea Gräf Date: Mon, 23 Dec 2024 23:02:08 +0100 Subject: feat: Add /dh command --- .../mixins/FirmKeybindsInVanillaControlsPatch.java | 2 +- .../mixins/SaveOriginalCommandTreePacket.java | 17 ++ src/main/kotlin/features/chat/QuickCommands.kt | 212 +++++++++++++-------- src/main/kotlin/gui/config/ManagedConfig.kt | 5 +- src/main/kotlin/gui/config/ManagedOption.kt | 9 +- src/main/resources/firmament.accesswidener | 1 + translations/en_us.json | 5 + 7 files changed, 167 insertions(+), 84 deletions(-) create mode 100644 src/main/java/moe/nea/firmament/mixins/SaveOriginalCommandTreePacket.java (limited to 'src/main/java') diff --git a/src/main/java/moe/nea/firmament/mixins/FirmKeybindsInVanillaControlsPatch.java b/src/main/java/moe/nea/firmament/mixins/FirmKeybindsInVanillaControlsPatch.java index b386604..699d5b7 100644 --- a/src/main/java/moe/nea/firmament/mixins/FirmKeybindsInVanillaControlsPatch.java +++ b/src/main/java/moe/nea/firmament/mixins/FirmKeybindsInVanillaControlsPatch.java @@ -51,7 +51,7 @@ public class FirmKeybindsInVanillaControlsPatch { var config = FirmamentKeyBindings.INSTANCE.getKeyBindings().get(binding); if (config == null) return; resetButton.active = false; - editButton.setMessage(Text.translatable("firmament.keybinding.external", config.value.format())); + editButton.setMessage(Text.translatable("firmament.keybinding.external", config.getValue().format())); ci.cancel(); } diff --git a/src/main/java/moe/nea/firmament/mixins/SaveOriginalCommandTreePacket.java b/src/main/java/moe/nea/firmament/mixins/SaveOriginalCommandTreePacket.java new file mode 100644 index 0000000..2f2f188 --- /dev/null +++ b/src/main/java/moe/nea/firmament/mixins/SaveOriginalCommandTreePacket.java @@ -0,0 +1,17 @@ +package moe.nea.firmament.mixins; + +import moe.nea.firmament.features.chat.QuickCommands; +import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.network.packet.s2c.play.CommandTreeS2CPacket; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(ClientPlayNetworkHandler.class) +public class SaveOriginalCommandTreePacket { + @Inject(method = "onCommandTree", at = @At(value = "RETURN")) + private void saveUnmodifiedCommandTree(CommandTreeS2CPacket packet, CallbackInfo ci) { + QuickCommands.INSTANCE.setLastReceivedTreePacket(packet); + } +} diff --git a/src/main/kotlin/features/chat/QuickCommands.kt b/src/main/kotlin/features/chat/QuickCommands.kt index 5944b92..7963171 100644 --- a/src/main/kotlin/features/chat/QuickCommands.kt +++ b/src/main/kotlin/features/chat/QuickCommands.kt @@ -1,8 +1,12 @@ - - package moe.nea.firmament.features.chat +import com.mojang.brigadier.CommandDispatcher import com.mojang.brigadier.context.CommandContext +import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource +import net.fabricmc.fabric.impl.command.client.ClientCommandInternals +import net.minecraft.command.CommandRegistryAccess +import net.minecraft.network.packet.s2c.play.CommandTreeS2CPacket import net.minecraft.text.Text import moe.nea.firmament.annotations.Subscribe import moe.nea.firmament.commands.DefaultSource @@ -12,89 +16,139 @@ import moe.nea.firmament.commands.thenArgument import moe.nea.firmament.commands.thenExecute import moe.nea.firmament.events.CommandEvent import moe.nea.firmament.features.FirmamentFeature +import moe.nea.firmament.gui.config.ManagedConfig +import moe.nea.firmament.gui.config.ManagedOption import moe.nea.firmament.util.MC import moe.nea.firmament.util.SBData +import moe.nea.firmament.util.grey +import moe.nea.firmament.util.tr object QuickCommands : FirmamentFeature { - override val identifier: String - get() = "quick-commands" + override val identifier: String + get() = "quick-commands" + + object TConfig : ManagedConfig("quick-commands", Category.CHAT) { + val enableJoin by toggle("join") { true } + val enableDh by toggle("dh") { true } + override fun onChange(option: ManagedOption<*>) { + reloadCommands() + } + } + + fun reloadCommands() { + val lastPacket = lastReceivedTreePacket ?: return + val network = MC.networkHandler ?: return + val fallback = ClientCommandInternals.getActiveDispatcher() + try { + val dispatcher = CommandDispatcher() + ClientCommandInternals.setActiveDispatcher(dispatcher) + ClientCommandRegistrationCallback.EVENT.invoker() + .register(dispatcher, CommandRegistryAccess.of(network.combinedDynamicRegistries, + network.enabledFeatures)) + ClientCommandInternals.finalizeInit() + network.onCommandTree(lastPacket) + } catch (ex: Exception) { + ClientCommandInternals.setActiveDispatcher(fallback) + throw ex + } + } + + + fun removePartialPrefix(text: String, prefix: String): String? { + var lf: String? = null + for (i in 1..prefix.length) { + if (text.startsWith(prefix.substring(0, i))) { + lf = text.substring(i) + } + } + return lf + } + + var lastReceivedTreePacket: CommandTreeS2CPacket? = null - fun removePartialPrefix(text: String, prefix: String): String? { - var lf: String? = null - for (i in 1..prefix.length) { - if (text.startsWith(prefix.substring(0, i))) { - lf = text.substring(i) - } - } - return lf - } + val kuudraLevelNames = listOf("NORMAL", "HOT", "BURNING", "FIERY", "INFERNAL") + val dungeonLevelNames = listOf("ONE", "TWO", "THREE", "FOUR", "FIVE", "SIX", "SEVEN") - val kuudraLevelNames = listOf("NORMAL", "HOT", "BURNING", "FIERY", "INFERNAL") - val dungeonLevelNames = listOf("ONE", "TWO", "THREE", "FOUR", "FIVE", "SIX", "SEVEN") + @Subscribe + fun registerDh(event: CommandEvent) { + if (!TConfig.enableDh) return + event.register("dh") { + thenExecute { + MC.sendCommand("warp dhub") + } + } + event.register("dn") { + thenExecute { + MC.sendChat(tr("firmament.quickwarp.deez-nutz", "Warping to... Deez Nuts!").grey()) + MC.sendCommand("warp dhub") + } + } + } - @Subscribe - fun onCommands(it: CommandEvent) { - it.register("join") { - thenArgument("what", RestArgumentType) { what -> - thenExecute { - val what = this[what] - if (!SBData.isOnSkyblock) { - MC.sendCommand("join $what") - return@thenExecute - } - val joinName = getNameForFloor(what.replace(" ", "").lowercase()) - if (joinName == null) { - source.sendFeedback(Text.stringifiedTranslatable("firmament.quick-commands.join.unknown", what)) - } else { - source.sendFeedback(Text.stringifiedTranslatable("firmament.quick-commands.join.success", - joinName)) - MC.sendCommand("joininstance $joinName") - } - } - } - thenExecute { - source.sendFeedback(Text.translatable("firmament.quick-commands.join.explain")) - } - } - } + @Subscribe + fun registerJoin(it: CommandEvent) { + if (!TConfig.enableJoin) return + it.register("join") { + thenArgument("what", RestArgumentType) { what -> + thenExecute { + val what = this[what] + if (!SBData.isOnSkyblock) { + MC.sendCommand("join $what") + return@thenExecute + } + val joinName = getNameForFloor(what.replace(" ", "").lowercase()) + if (joinName == null) { + source.sendFeedback(Text.stringifiedTranslatable("firmament.quick-commands.join.unknown", what)) + } else { + source.sendFeedback(Text.stringifiedTranslatable("firmament.quick-commands.join.success", + joinName)) + MC.sendCommand("joininstance $joinName") + } + } + } + thenExecute { + source.sendFeedback(Text.translatable("firmament.quick-commands.join.explain")) + } + } + } - fun CommandContext.getNameForFloor(w: String): String? { - val kuudraLevel = removePartialPrefix(w, "kuudratier") ?: removePartialPrefix(w, "tier") - if (kuudraLevel != null) { - val l = kuudraLevel.toIntOrNull()?.let { it - 1 } ?: kuudraLevelNames.indexOfFirst { - it.startsWith( - kuudraLevel, - true - ) - } - if (l !in kuudraLevelNames.indices) { - source.sendFeedback(Text.stringifiedTranslatable("firmament.quick-commands.join.unknown-kuudra", - kuudraLevel)) - return null - } - return "KUUDRA_${kuudraLevelNames[l]}" - } - val masterLevel = removePartialPrefix(w, "master") - val normalLevel = - removePartialPrefix(w, "floor") ?: removePartialPrefix(w, "catacombs") ?: removePartialPrefix(w, "dungeons") - val dungeonLevel = masterLevel ?: normalLevel - if (dungeonLevel != null) { - val l = dungeonLevel.toIntOrNull()?.let { it - 1 } ?: dungeonLevelNames.indexOfFirst { - it.startsWith( - dungeonLevel, - true - ) - } - if (masterLevel == null && (l == -1 || null != removePartialPrefix(w, "entrance"))) { - return "CATACOMBS_ENTRANCE" - } - if (l !in dungeonLevelNames.indices) { - source.sendFeedback(Text.stringifiedTranslatable("firmament.quick-commands.join.unknown-catacombs", - kuudraLevel)) - return null - } - return "${if (masterLevel != null) "MASTER_" else ""}CATACOMBS_FLOOR_${dungeonLevelNames[l]}" - } - return null - } + fun CommandContext.getNameForFloor(w: String): String? { + val kuudraLevel = removePartialPrefix(w, "kuudratier") ?: removePartialPrefix(w, "tier") + if (kuudraLevel != null) { + val l = kuudraLevel.toIntOrNull()?.let { it - 1 } ?: kuudraLevelNames.indexOfFirst { + it.startsWith( + kuudraLevel, + true + ) + } + if (l !in kuudraLevelNames.indices) { + source.sendFeedback(Text.stringifiedTranslatable("firmament.quick-commands.join.unknown-kuudra", + kuudraLevel)) + return null + } + return "KUUDRA_${kuudraLevelNames[l]}" + } + val masterLevel = removePartialPrefix(w, "master") + val normalLevel = + removePartialPrefix(w, "floor") ?: removePartialPrefix(w, "catacombs") ?: removePartialPrefix(w, "dungeons") + val dungeonLevel = masterLevel ?: normalLevel + if (dungeonLevel != null) { + val l = dungeonLevel.toIntOrNull()?.let { it - 1 } ?: dungeonLevelNames.indexOfFirst { + it.startsWith( + dungeonLevel, + true + ) + } + if (masterLevel == null && (l == -1 || null != removePartialPrefix(w, "entrance"))) { + return "CATACOMBS_ENTRANCE" + } + if (l !in dungeonLevelNames.indices) { + source.sendFeedback(Text.stringifiedTranslatable("firmament.quick-commands.join.unknown-catacombs", + kuudraLevel)) + return null + } + return "${if (masterLevel != null) "MASTER_" else ""}CATACOMBS_FLOOR_${dungeonLevelNames[l]}" + } + return null + } } diff --git a/src/main/kotlin/gui/config/ManagedConfig.kt b/src/main/kotlin/gui/config/ManagedConfig.kt index 641b673..7ddda9e 100644 --- a/src/main/kotlin/gui/config/ManagedConfig.kt +++ b/src/main/kotlin/gui/config/ManagedConfig.kt @@ -15,7 +15,6 @@ import org.lwjgl.glfw.GLFW import kotlinx.serialization.encodeToString import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonObject -import kotlin.enums.enumEntries import kotlin.io.path.createDirectories import kotlin.io.path.readText import kotlin.io.path.writeText @@ -135,7 +134,7 @@ abstract class ManagedConfig( return StringIdentifiable.createCodec { x() } } -// TODO: wait on https://youtrack.jetbrains.com/issue/KT-73434 + // TODO: wait on https://youtrack.jetbrains.com/issue/KT-73434 // protected inline fun choice( // propertyName: String, // noinline default: () -> E @@ -148,6 +147,8 @@ abstract class ManagedConfig( // default // ) // } + open fun onChange(option: ManagedOption<*>) { + } protected fun duration( propertyName: String, diff --git a/src/main/kotlin/gui/config/ManagedOption.kt b/src/main/kotlin/gui/config/ManagedOption.kt index d1aba83..383f392 100644 --- a/src/main/kotlin/gui/config/ManagedOption.kt +++ b/src/main/kotlin/gui/config/ManagedOption.kt @@ -6,7 +6,6 @@ import kotlinx.serialization.json.JsonObject import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty import net.minecraft.text.Text -import moe.nea.firmament.Firmament import moe.nea.firmament.util.ErrorUtil class ManagedOption( @@ -28,7 +27,13 @@ class ManagedOption( val descriptionTranslationKey = "firmament.config.${element.name}.${propertyName}.description" val labelDescription: Text = Text.translatable(descriptionTranslationKey) - lateinit var value: T + private var actualValue: T? = null + var value: T + get() = actualValue ?: error("Lateinit variable not initialized") + set(value) { + actualValue = value + element.onChange(this) + } override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { this.value = value diff --git a/src/main/resources/firmament.accesswidener b/src/main/resources/firmament.accesswidener index 8e7dbab..cfa8e90 100644 --- a/src/main/resources/firmament.accesswidener +++ b/src/main/resources/firmament.accesswidener @@ -3,6 +3,7 @@ accessible class net/minecraft/client/render/RenderLayer$MultiPhase accessible class net/minecraft/client/render/RenderLayer$MultiPhaseParameters accessible class net/minecraft/client/font/TextRenderer$Drawer accessible field net/minecraft/client/gui/hud/InGameHud SCOREBOARD_ENTRY_COMPARATOR Ljava/util/Comparator; +accessible field net/minecraft/client/network/ClientPlayNetworkHandler combinedDynamicRegistries Lnet/minecraft/registry/DynamicRegistryManager$Immutable; accessible field net/minecraft/client/render/item/HeldItemRenderer itemRenderer Lnet/minecraft/client/render/item/ItemRenderer; accessible field net/minecraft/client/render/item/ItemModels missingModelSupplier Ljava/util/function/Supplier; diff --git a/translations/en_us.json b/translations/en_us.json index d1af11e..a157432 100644 --- a/translations/en_us.json +++ b/translations/en_us.json @@ -180,6 +180,11 @@ "firmament.config.pristine-profit.position.description": "Edit the pristine profit hud location.", "firmament.config.pristine-profit.timeout": "Timeout (0 = disabled)", "firmament.config.pristine-profit.timeout.description": "Track the profit you make from pristine gemstones while mining. Set to 0 seconds to disable the HUD.", + "firmament.config.quick-commands": "Quick Commands", + "firmament.config.quick-commands.dh": "Enable /dh", + "firmament.config.quick-commands.dh.description": "Warps you to the dungeon hub.", + "firmament.config.quick-commands.join": "Enable /join", + "firmament.config.quick-commands.join.description": "Join various types of instances like dungeons using short hands like /join f1, /join k1, /join m7", "firmament.config.repo": "Firmament Repo Settings", "firmament.config.repo.autoUpdate": "Auto Update", "firmament.config.repo.autoUpdate.description": "Automatically download new items for the item list on every startup.", -- cgit From 39d35afb702cf017569ef9594774561848db7494 Mon Sep 17 00:00:00 2001 From: Linnea Gräf Date: Mon, 23 Dec 2024 23:53:27 +0100 Subject: fix: Some items not being saved in /firm stoarge --- .../TolerateFirmamentTolerateRegistryOwners.java | 18 +++++ .../inventory/storageoverlay/VirtualInventory.kt | 82 ++++++++++++---------- src/main/kotlin/util/mc/TolerantRegistriesOps.kt | 29 ++++++++ src/main/resources/firmament.accesswidener | 2 + 4 files changed, 95 insertions(+), 36 deletions(-) create mode 100644 src/main/java/moe/nea/firmament/mixins/TolerateFirmamentTolerateRegistryOwners.java create mode 100644 src/main/kotlin/util/mc/TolerantRegistriesOps.kt (limited to 'src/main/java') diff --git a/src/main/java/moe/nea/firmament/mixins/TolerateFirmamentTolerateRegistryOwners.java b/src/main/java/moe/nea/firmament/mixins/TolerateFirmamentTolerateRegistryOwners.java new file mode 100644 index 0000000..ac6f614 --- /dev/null +++ b/src/main/java/moe/nea/firmament/mixins/TolerateFirmamentTolerateRegistryOwners.java @@ -0,0 +1,18 @@ +package moe.nea.firmament.mixins; + +import moe.nea.firmament.util.mc.TolerantRegistriesOps; +import net.minecraft.registry.entry.RegistryEntryOwner; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(RegistryEntryOwner.class) +public interface TolerateFirmamentTolerateRegistryOwners { + @Inject(method = "ownerEquals", at = @At("HEAD"), cancellable = true) + private void equalTolerantRegistryOwners(RegistryEntryOwner other, CallbackInfoReturnable cir) { + if (other instanceof TolerantRegistriesOps.TolerantOwner) { + cir.setReturnValue(true); + } + } +} diff --git a/src/main/kotlin/features/inventory/storageoverlay/VirtualInventory.kt b/src/main/kotlin/features/inventory/storageoverlay/VirtualInventory.kt index e07df8a..3b86184 100644 --- a/src/main/kotlin/features/inventory/storageoverlay/VirtualInventory.kt +++ b/src/main/kotlin/features/inventory/storageoverlay/VirtualInventory.kt @@ -1,5 +1,3 @@ - - package moe.nea.firmament.features.inventory.storageoverlay import io.ktor.util.decodeBase64Bytes @@ -19,47 +17,59 @@ import net.minecraft.nbt.NbtIo import net.minecraft.nbt.NbtList import net.minecraft.nbt.NbtOps import net.minecraft.nbt.NbtSizeTracker +import net.minecraft.registry.RegistryOps +import moe.nea.firmament.util.ErrorUtil +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.mc.TolerantRegistriesOps @Serializable(with = VirtualInventory.Serializer::class) data class VirtualInventory( - val stacks: List + val stacks: List ) { - val rows = stacks.size / 9 + val rows = stacks.size / 9 + + init { + assert(stacks.size % 9 == 0) + assert(stacks.size / 9 in 1..5) + } - init { - assert(stacks.size % 9 == 0) - assert(stacks.size / 9 in 1..5) - } + object Serializer : KSerializer { + const val INVENTORY = "INVENTORY" + override val descriptor: SerialDescriptor + get() = PrimitiveSerialDescriptor("VirtualInventory", PrimitiveKind.STRING) - object Serializer : KSerializer { - const val INVENTORY = "INVENTORY" - override val descriptor: SerialDescriptor - get() = PrimitiveSerialDescriptor("VirtualInventory", PrimitiveKind.STRING) + override fun deserialize(decoder: Decoder): VirtualInventory { + val s = decoder.decodeString() + val n = NbtIo.readCompressed(ByteArrayInputStream(s.decodeBase64Bytes()), NbtSizeTracker.of(100_000_000)) + val items = n.getList(INVENTORY, NbtCompound.COMPOUND_TYPE.toInt()) + val ops = getOps() + return VirtualInventory(items.map { + it as NbtCompound + if (it.isEmpty) ItemStack.EMPTY + else ErrorUtil.catch("Could not deserialize item") { + ItemStack.CODEC.parse(ops, it).orThrow + }.or { ItemStack.EMPTY } + }) + } - override fun deserialize(decoder: Decoder): VirtualInventory { - val s = decoder.decodeString() - val n = NbtIo.readCompressed(ByteArrayInputStream(s.decodeBase64Bytes()), NbtSizeTracker.of(100_000_000)) - val items = n.getList(INVENTORY, NbtCompound.COMPOUND_TYPE.toInt()) - return VirtualInventory(items.map { - it as NbtCompound - if (it.isEmpty) ItemStack.EMPTY - else runCatching { - ItemStack.CODEC.parse(NbtOps.INSTANCE, it).orThrow - }.getOrElse { ItemStack.EMPTY } - }) - } + fun getOps() = TolerantRegistriesOps(NbtOps.INSTANCE, MC.currentOrDefaultRegistries) - override fun serialize(encoder: Encoder, value: VirtualInventory) { - val list = NbtList() - value.stacks.forEach { - if (it.isEmpty) list.add(NbtCompound()) - else list.add(runCatching { ItemStack.CODEC.encode(it, NbtOps.INSTANCE, NbtCompound()).orThrow } - .getOrElse { NbtCompound() }) - } - val baos = ByteArrayOutputStream() - NbtIo.writeCompressed(NbtCompound().also { it.put(INVENTORY, list) }, baos) - encoder.encodeString(baos.toByteArray().encodeBase64()) - } - } + override fun serialize(encoder: Encoder, value: VirtualInventory) { + val list = NbtList() + val ops = getOps() + value.stacks.forEach { + if (it.isEmpty) list.add(NbtCompound()) + else list.add(ErrorUtil.catch("Could not serialize item") { + ItemStack.CODEC.encode(it, + ops, + NbtCompound()).orThrow + } + .or { NbtCompound() }) + } + val baos = ByteArrayOutputStream() + NbtIo.writeCompressed(NbtCompound().also { it.put(INVENTORY, list) }, baos) + encoder.encodeString(baos.toByteArray().encodeBase64()) + } + } } diff --git a/src/main/kotlin/util/mc/TolerantRegistriesOps.kt b/src/main/kotlin/util/mc/TolerantRegistriesOps.kt new file mode 100644 index 0000000..ce596a0 --- /dev/null +++ b/src/main/kotlin/util/mc/TolerantRegistriesOps.kt @@ -0,0 +1,29 @@ +package moe.nea.firmament.util.mc + +import com.mojang.serialization.DynamicOps +import java.util.Optional +import net.minecraft.registry.Registry +import net.minecraft.registry.RegistryKey +import net.minecraft.registry.RegistryOps +import net.minecraft.registry.RegistryWrapper +import net.minecraft.registry.entry.RegistryEntryOwner + +class TolerantRegistriesOps( + delegate: DynamicOps, + registryInfoGetter: RegistryInfoGetter +) : RegistryOps(delegate, registryInfoGetter) { + constructor(delegate: DynamicOps, registry: RegistryWrapper.WrapperLookup) : + this(delegate, CachedRegistryInfoGetter(registry)) + + class TolerantOwner : RegistryEntryOwner { + override fun ownerEquals(other: RegistryEntryOwner?): Boolean { + return true + } + } + + override fun getOwner(registryRef: RegistryKey>?): Optional> { + return super.getOwner(registryRef).map { + TolerantOwner() + } + } +} diff --git a/src/main/resources/firmament.accesswidener b/src/main/resources/firmament.accesswidener index cfa8e90..b280087 100644 --- a/src/main/resources/firmament.accesswidener +++ b/src/main/resources/firmament.accesswidener @@ -4,6 +4,8 @@ accessible class net/minecraft/client/render/RenderLayer$MultiPhaseParameters accessible class net/minecraft/client/font/TextRenderer$Drawer accessible field net/minecraft/client/gui/hud/InGameHud SCOREBOARD_ENTRY_COMPARATOR Ljava/util/Comparator; accessible field net/minecraft/client/network/ClientPlayNetworkHandler combinedDynamicRegistries Lnet/minecraft/registry/DynamicRegistryManager$Immutable; +accessible method net/minecraft/registry/RegistryOps (Lcom/mojang/serialization/DynamicOps;Lnet/minecraft/registry/RegistryOps$RegistryInfoGetter;)V +accessible class net/minecraft/registry/RegistryOps$CachedRegistryInfoGetter accessible field net/minecraft/client/render/item/HeldItemRenderer itemRenderer Lnet/minecraft/client/render/item/ItemRenderer; accessible field net/minecraft/client/render/item/ItemModels missingModelSupplier Ljava/util/function/Supplier; -- cgit From 24110c24af3ed14c0da050a2cdb1053b183b30b6 Mon Sep 17 00:00:00 2001 From: Linnea Gräf Date: Tue, 24 Dec 2024 01:01:10 +0100 Subject: feat: Add /firm timer command --- .../AlwaysDisplayFirmamentClientCommandErrors.java | 18 +++ src/main/kotlin/commands/Duration.kt | 75 +++++++++++++ src/main/kotlin/features/misc/TimerFeature.kt | 124 +++++++++++++++++++++ src/main/kotlin/util/textutil.kt | 3 +- 4 files changed, 218 insertions(+), 2 deletions(-) create mode 100644 src/main/java/moe/nea/firmament/mixins/AlwaysDisplayFirmamentClientCommandErrors.java create mode 100644 src/main/kotlin/commands/Duration.kt create mode 100644 src/main/kotlin/features/misc/TimerFeature.kt (limited to 'src/main/java') diff --git a/src/main/java/moe/nea/firmament/mixins/AlwaysDisplayFirmamentClientCommandErrors.java b/src/main/java/moe/nea/firmament/mixins/AlwaysDisplayFirmamentClientCommandErrors.java new file mode 100644 index 0000000..59769c6 --- /dev/null +++ b/src/main/java/moe/nea/firmament/mixins/AlwaysDisplayFirmamentClientCommandErrors.java @@ -0,0 +1,18 @@ +package moe.nea.firmament.mixins; + +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import com.llamalad7.mixinextras.sugar.Local; +import net.fabricmc.fabric.impl.command.client.ClientCommandInternals; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(ClientCommandInternals.class) +public class AlwaysDisplayFirmamentClientCommandErrors { + @ModifyExpressionValue(method = "executeCommand", at = @At(value = "INVOKE", target = "Lnet/fabricmc/fabric/impl/command/client/ClientCommandInternals;isIgnoredException(Lcom/mojang/brigadier/exceptions/CommandExceptionType;)Z")) + private static boolean markFirmamentExceptionsAsNotIgnores(boolean original, @Local(argsOnly = true) String command) { + if (command.startsWith("firm ") || command.equals("firm") || command.startsWith("firmament ") || command.equals("firmament")) { + return false; + } + return original; + } +} diff --git a/src/main/kotlin/commands/Duration.kt b/src/main/kotlin/commands/Duration.kt new file mode 100644 index 0000000..42f143d --- /dev/null +++ b/src/main/kotlin/commands/Duration.kt @@ -0,0 +1,75 @@ +package moe.nea.firmament.commands + +import com.mojang.brigadier.StringReader +import com.mojang.brigadier.arguments.ArgumentType +import com.mojang.brigadier.context.CommandContext +import com.mojang.brigadier.exceptions.DynamicCommandExceptionType +import com.mojang.brigadier.suggestion.Suggestions +import com.mojang.brigadier.suggestion.SuggestionsBuilder +import java.util.concurrent.CompletableFuture +import java.util.function.Function +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds +import kotlin.time.DurationUnit +import kotlin.time.toDuration +import moe.nea.firmament.util.tr + +object DurationArgumentType : ArgumentType { + val unknownTimeCode = DynamicCommandExceptionType { timeCode -> + tr("firmament.command-argument.duration.error", + "Unknown time code '$timeCode'") + } + + override fun parse(reader: StringReader): Duration { + val start = reader.cursor + val string = reader.readUnquotedString() + val matcher = regex.matcher(string) + var s = 0 + var time = 0.seconds + fun createError(till: Int) { + throw unknownTimeCode.createWithContext( + reader.also { it.cursor = start + s }, + string.substring(s, till)) + } + + while (matcher.find()) { + if (matcher.start() != s) { + createError(matcher.start()) + } + s = matcher.end() + val amount = matcher.group("count").toDouble() + val what = timeSuffixes[matcher.group("what").single()]!! + time += amount.toDuration(what) + } + if (string.length != s) { + createError(string.length) + } + return time + } + + + override fun listSuggestions( + context: CommandContext, + builder: SuggestionsBuilder + ): CompletableFuture { + val remaining = builder.remainingLowerCase.substringBefore(' ') + if (remaining.isEmpty()) return super.listSuggestions(context, builder) + if (remaining.last().isDigit()) { + for (timeSuffix in timeSuffixes.keys) { + builder.suggest(remaining + timeSuffix) + } + } + return builder.buildFuture() + } + + val timeSuffixes = mapOf( + 'm' to DurationUnit.MINUTES, + 's' to DurationUnit.SECONDS, + 'h' to DurationUnit.HOURS, + ) + val regex = "(?[0-9]+)(?[${timeSuffixes.keys.joinToString("")}])".toPattern() + + override fun getExamples(): Collection { + return listOf("3m", "20s", "1h45m") + } +} diff --git a/src/main/kotlin/features/misc/TimerFeature.kt b/src/main/kotlin/features/misc/TimerFeature.kt new file mode 100644 index 0000000..7c4833d --- /dev/null +++ b/src/main/kotlin/features/misc/TimerFeature.kt @@ -0,0 +1,124 @@ +package moe.nea.firmament.features.misc + +import com.mojang.brigadier.arguments.IntegerArgumentType +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds +import moe.nea.firmament.Firmament +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.commands.DurationArgumentType +import moe.nea.firmament.commands.RestArgumentType +import moe.nea.firmament.commands.get +import moe.nea.firmament.commands.thenArgument +import moe.nea.firmament.commands.thenExecute +import moe.nea.firmament.events.CommandEvent +import moe.nea.firmament.events.TickEvent +import moe.nea.firmament.util.CommonSoundEffects +import moe.nea.firmament.util.FirmFormatters +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.MinecraftDispatcher +import moe.nea.firmament.util.TimeMark +import moe.nea.firmament.util.clickCommand +import moe.nea.firmament.util.lime +import moe.nea.firmament.util.red +import moe.nea.firmament.util.tr +import moe.nea.firmament.util.yellow + +object TimerFeature { + data class Timer( + val start: TimeMark, + val duration: Duration, + val message: String, + val timerId: Int, + ) { + fun timeLeft() = (duration - start.passedTime()).coerceAtLeast(0.seconds) + fun isDone() = start.passedTime() >= duration + } + + // Theoretically for optimal performance this could be a treeset keyed to the end time + val timers = mutableListOf() + + @Subscribe + fun tick(event: TickEvent) { + timers.removeAll { + if (it.isDone()) { + MC.sendChat(tr("firmament.timer.finished", + "The timer you set ${FirmFormatters.formatTimespan(it.duration)} ago just went off: ${it.message}") + .yellow()) + Firmament.coroutineScope.launch { + withContext(MinecraftDispatcher) { + repeat(5) { + CommonSoundEffects.playSuccess() + delay(0.2.seconds) + } + } + } + true + } else { + false + } + } + } + + fun startTimer(duration: Duration, message: String) { + val timerId = createTimerId++ + timers.add(Timer(TimeMark.now(), duration, message, timerId)) + MC.sendChat( + tr("firmament.timer.start", + "Timer started for $message in ${FirmFormatters.formatTimespan(duration)}.").lime() + .append(" ") + .append( + tr("firmament.timer.cancelbutton", + "Click here to cancel the timer." + ).clickCommand("/firm timer clear $timerId").red() + ) + ) + } + + fun clearTimer(timerId: Int) { + val timer = timers.indexOfFirst { it.timerId == timerId } + if (timer < 0) { + MC.sendChat(tr("firmament.timer.cancel.fail", + "Could not cancel that timer. Maybe it was already cancelled?").red()) + } else { + val timerData = timers[timer] + timers.removeAt(timer) + MC.sendChat(tr("firmament.timer.cancel.done", + "Cancelled timer ${timerData.message}. It would have been done in ${ + FirmFormatters.formatTimespan(timerData.timeLeft()) + }.").lime()) + } + } + + var createTimerId = 0 + + @Subscribe + fun onCommands(event: CommandEvent.SubCommand) { + event.subcommand("cleartimer") { + thenArgument("timerId", IntegerArgumentType.integer(0)) { timerId -> + thenExecute { + clearTimer(this[timerId]) + } + } + thenExecute { + timers.map { it.timerId }.forEach { + clearTimer(it) + } + } + } + event.subcommand("timer") { + thenArgument("time", DurationArgumentType) { duration -> + thenExecute { + startTimer(this[duration], "no message") + } + thenArgument("message", RestArgumentType) { message -> + thenExecute { + startTimer(this[duration], this[message]) + } + } + } + } + } +} diff --git a/src/main/kotlin/util/textutil.kt b/src/main/kotlin/util/textutil.kt index 5d95d7a..06ed8c8 100644 --- a/src/main/kotlin/util/textutil.kt +++ b/src/main/kotlin/util/textutil.kt @@ -142,8 +142,7 @@ fun MutableText.bold(): MutableText = styled { it.withBold(true) } fun MutableText.clickCommand(command: String): MutableText { require(command.startsWith("/")) return this.styled { - it.withClickEvent(ClickEvent(ClickEvent.Action.RUN_COMMAND, - "/firm disablereiwarning")) + it.withClickEvent(ClickEvent(ClickEvent.Action.RUN_COMMAND, command)) } } -- cgit