Merge branch 'mc-1.21.3'
This commit is contained in:
134
src/main/kotlin/features/chat/PartyCommands.kt
Normal file
134
src/main/kotlin/features/chat/PartyCommands.kt
Normal file
@@ -0,0 +1,134 @@
|
||||
package moe.nea.firmament.features.chat
|
||||
|
||||
import com.mojang.brigadier.CommandDispatcher
|
||||
import com.mojang.brigadier.StringReader
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException
|
||||
import com.mojang.brigadier.tree.LiteralCommandNode
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import moe.nea.firmament.annotations.Subscribe
|
||||
import moe.nea.firmament.commands.CaseInsensitiveLiteralCommandNode
|
||||
import moe.nea.firmament.commands.thenExecute
|
||||
import moe.nea.firmament.events.CommandEvent
|
||||
import moe.nea.firmament.events.PartyMessageReceivedEvent
|
||||
import moe.nea.firmament.events.ProcessChatEvent
|
||||
import moe.nea.firmament.gui.config.ManagedConfig
|
||||
import moe.nea.firmament.util.ErrorUtil
|
||||
import moe.nea.firmament.util.MC
|
||||
import moe.nea.firmament.util.TimeMark
|
||||
import moe.nea.firmament.util.tr
|
||||
import moe.nea.firmament.util.useMatch
|
||||
|
||||
object PartyCommands {
|
||||
|
||||
val messageInChannel = "(?<channel>Party|Guild) >([^:]+?)? (?<name>[^: ]+): (?<message>.+)".toPattern()
|
||||
|
||||
@Subscribe
|
||||
fun onChat(event: ProcessChatEvent) {
|
||||
messageInChannel.useMatch(event.unformattedString) {
|
||||
val channel = group("channel")
|
||||
val message = group("message")
|
||||
val name = group("name")
|
||||
if (channel == "Party") {
|
||||
PartyMessageReceivedEvent.publish(PartyMessageReceivedEvent(
|
||||
event, message, name
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val commandPrefixes = "!-?$.&#+~€\"@°_;:³²`'´ß\\,|".toSet()
|
||||
|
||||
data class PartyCommandContext(
|
||||
val name: String
|
||||
)
|
||||
|
||||
val dispatch = CommandDispatcher<PartyCommandContext>().also { dispatch ->
|
||||
fun register(
|
||||
name: String,
|
||||
vararg alias: String,
|
||||
block: CaseInsensitiveLiteralCommandNode.Builder<PartyCommandContext>.() -> Unit = {},
|
||||
): LiteralCommandNode<PartyCommandContext> {
|
||||
val node =
|
||||
dispatch.register(CaseInsensitiveLiteralCommandNode.Builder<PartyCommandContext>(name).also(block))
|
||||
alias.forEach { register(it) { redirect(node) } }
|
||||
return node
|
||||
}
|
||||
|
||||
register("warp", "pw", "pwarp", "partywarp") {
|
||||
executes {
|
||||
// TODO: add check if you are the party leader
|
||||
MC.sendCommand("p warp")
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
register("transfer", "pt", "ptme") {
|
||||
executes {
|
||||
MC.sendCommand("p transfer ${it.source.name}")
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
register("allinvite", "allinv") {
|
||||
executes {
|
||||
MC.sendCommand("p settings allinvite")
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
register("coords") {
|
||||
executes {
|
||||
val p = MC.player?.blockPos ?: BlockPos.ORIGIN
|
||||
MC.sendCommand("pc x: ${p.x}, y: ${p.y}, z: ${p.z}")
|
||||
0
|
||||
}
|
||||
}
|
||||
// TODO: downtime tracker (display message again at end of dungeon)
|
||||
// instance ends: kuudra, dungeons, bacte
|
||||
// TODO: at TPS command
|
||||
}
|
||||
|
||||
object TConfig : ManagedConfig("party-commands", Category.CHAT) {
|
||||
val enable by toggle("enable") { false }
|
||||
val cooldown by duration("cooldown", 0.seconds, 20.seconds) { 2.seconds }
|
||||
val ignoreOwnCommands by toggle("ignore-own") { false }
|
||||
}
|
||||
|
||||
var lastCommand = TimeMark.farPast()
|
||||
|
||||
@Subscribe
|
||||
fun listPartyCommands(event: CommandEvent.SubCommand) {
|
||||
event.subcommand("partycommands") {
|
||||
thenExecute {
|
||||
// TODO: Better help, including descriptions and redirect detection
|
||||
MC.sendChat(tr("firmament.partycommands.help", "Available party commands: ${dispatch.root.children.map { it.name }}. Available prefixes: $commandPrefixes"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
fun onPartyMessage(event: PartyMessageReceivedEvent) {
|
||||
if (!TConfig.enable) return
|
||||
if (event.message.firstOrNull() !in commandPrefixes) return
|
||||
if (event.name == MC.playerName && TConfig.ignoreOwnCommands) return
|
||||
if (lastCommand.passedTime() < TConfig.cooldown) {
|
||||
MC.sendChat(tr("firmament.partycommands.cooldown", "Skipping party command. Cooldown not passed."))
|
||||
return
|
||||
}
|
||||
// TODO: add trust levels
|
||||
val commandLine = event.message.substring(1)
|
||||
try {
|
||||
dispatch.execute(StringReader(commandLine), PartyCommandContext(event.name))
|
||||
} catch (ex: Exception) {
|
||||
if (ex is CommandSyntaxException) {
|
||||
MC.sendChat(tr("firmament.partycommands.unknowncommand", "Unknown party command."))
|
||||
return
|
||||
} else {
|
||||
MC.sendChat(tr("firmament.partycommands.unknownerror", "Unknown error during command execution."))
|
||||
ErrorUtil.softError("Unknown error during command execution.", ex)
|
||||
}
|
||||
}
|
||||
lastCommand = TimeMark.now()
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
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
|
||||
}
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
val kuudraLevelNames = listOf("NORMAL", "HOT", "BURNING", "FIERY", "INFERNAL")
|
||||
val dungeonLevelNames = listOf("ONE", "TWO", "THREE", "FOUR", "FIVE", "SIX", "SEVEN")
|
||||
fun reloadCommands() {
|
||||
val lastPacket = lastReceivedTreePacket ?: return
|
||||
val network = MC.networkHandler ?: return
|
||||
val fallback = ClientCommandInternals.getActiveDispatcher()
|
||||
try {
|
||||
val dispatcher = CommandDispatcher<FabricClientCommandSource>()
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@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"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun CommandContext<DefaultSource>.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 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
|
||||
|
||||
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 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<DefaultSource>.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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,71 +1,67 @@
|
||||
|
||||
|
||||
package moe.nea.firmament.features.fixes
|
||||
|
||||
import moe.nea.jarvis.api.Point
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.minecraft.client.option.KeyBinding
|
||||
import net.minecraft.entity.player.PlayerEntity
|
||||
import net.minecraft.text.Text
|
||||
import net.minecraft.util.Arm
|
||||
import moe.nea.firmament.annotations.Subscribe
|
||||
import moe.nea.firmament.events.HudRenderEvent
|
||||
import moe.nea.firmament.events.WorldKeyboardEvent
|
||||
import moe.nea.firmament.features.FirmamentFeature
|
||||
import moe.nea.firmament.gui.config.ManagedConfig
|
||||
import moe.nea.firmament.util.MC
|
||||
import moe.nea.firmament.util.errorBoundary
|
||||
|
||||
object Fixes : FirmamentFeature {
|
||||
override val identifier: String
|
||||
get() = "fixes"
|
||||
override val identifier: String
|
||||
get() = "fixes"
|
||||
|
||||
object TConfig : ManagedConfig(identifier, Category.MISC) { // TODO: split this config
|
||||
val fixUnsignedPlayerSkins by toggle("player-skins") { true }
|
||||
var autoSprint by toggle("auto-sprint") { false }
|
||||
val autoSprintKeyBinding by keyBindingWithDefaultUnbound("auto-sprint-keybinding")
|
||||
val autoSprintHud by position("auto-sprint-hud", 80, 10) { Point(0.0, 1.0) }
|
||||
val peekChat by keyBindingWithDefaultUnbound("peek-chat")
|
||||
}
|
||||
object TConfig : ManagedConfig(identifier, Category.MISC) { // TODO: split this config
|
||||
val fixUnsignedPlayerSkins by toggle("player-skins") { true }
|
||||
var autoSprint by toggle("auto-sprint") { false }
|
||||
val autoSprintKeyBinding by keyBindingWithDefaultUnbound("auto-sprint-keybinding")
|
||||
val autoSprintHud by position("auto-sprint-hud", 80, 10) { Point(0.0, 1.0) }
|
||||
val peekChat by keyBindingWithDefaultUnbound("peek-chat")
|
||||
val hidePotionEffects by toggle("hide-mob-effects") { false }
|
||||
}
|
||||
|
||||
override val config: ManagedConfig
|
||||
get() = TConfig
|
||||
override val config: ManagedConfig
|
||||
get() = TConfig
|
||||
|
||||
fun handleIsPressed(
|
||||
keyBinding: KeyBinding,
|
||||
cir: CallbackInfoReturnable<Boolean>
|
||||
) {
|
||||
if (keyBinding === MinecraftClient.getInstance().options.sprintKey && TConfig.autoSprint && MC.player?.isSprinting != true)
|
||||
cir.returnValue = true
|
||||
}
|
||||
fun handleIsPressed(
|
||||
keyBinding: KeyBinding,
|
||||
cir: CallbackInfoReturnable<Boolean>
|
||||
) {
|
||||
if (keyBinding === MinecraftClient.getInstance().options.sprintKey && TConfig.autoSprint && MC.player?.isSprinting != true)
|
||||
cir.returnValue = true
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
fun onRenderHud(it: HudRenderEvent) {
|
||||
if (!TConfig.autoSprintKeyBinding.isBound) return
|
||||
it.context.matrices.push()
|
||||
TConfig.autoSprintHud.applyTransformations(it.context.matrices)
|
||||
it.context.drawText(
|
||||
MC.font, Text.translatable(
|
||||
if (TConfig.autoSprint)
|
||||
"firmament.fixes.auto-sprint.on"
|
||||
else if (MC.player?.isSprinting == true)
|
||||
"firmament.fixes.auto-sprint.sprinting"
|
||||
else
|
||||
"firmament.fixes.auto-sprint.not-sprinting"
|
||||
), 0, 0, -1, false
|
||||
)
|
||||
it.context.matrices.pop()
|
||||
}
|
||||
@Subscribe
|
||||
fun onRenderHud(it: HudRenderEvent) {
|
||||
if (!TConfig.autoSprintKeyBinding.isBound) return
|
||||
it.context.matrices.push()
|
||||
TConfig.autoSprintHud.applyTransformations(it.context.matrices)
|
||||
it.context.drawText(
|
||||
MC.font, Text.translatable(
|
||||
if (TConfig.autoSprint)
|
||||
"firmament.fixes.auto-sprint.on"
|
||||
else if (MC.player?.isSprinting == true)
|
||||
"firmament.fixes.auto-sprint.sprinting"
|
||||
else
|
||||
"firmament.fixes.auto-sprint.not-sprinting"
|
||||
), 0, 0, -1, false
|
||||
)
|
||||
it.context.matrices.pop()
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
fun onWorldKeyboard(it: WorldKeyboardEvent) {
|
||||
if (it.matches(TConfig.autoSprintKeyBinding)) {
|
||||
TConfig.autoSprint = !TConfig.autoSprint
|
||||
}
|
||||
}
|
||||
@Subscribe
|
||||
fun onWorldKeyboard(it: WorldKeyboardEvent) {
|
||||
if (it.matches(TConfig.autoSprintKeyBinding)) {
|
||||
TConfig.autoSprint = !TConfig.autoSprint
|
||||
}
|
||||
}
|
||||
|
||||
fun shouldPeekChat(): Boolean {
|
||||
return TConfig.peekChat.isPressed(atLeast = true)
|
||||
}
|
||||
fun shouldPeekChat(): Boolean {
|
||||
return TConfig.peekChat.isPressed(atLeast = true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,18 +29,7 @@ object ItemRarityCosmetics : FirmamentFeature {
|
||||
override val config: ManagedConfig
|
||||
get() = TConfig
|
||||
|
||||
private val rarityToColor = mapOf(
|
||||
Rarity.COMMON to Formatting.WHITE,
|
||||
Rarity.UNCOMMON to Formatting.GREEN,
|
||||
Rarity.RARE to Formatting.BLUE,
|
||||
Rarity.EPIC to Formatting.DARK_PURPLE,
|
||||
Rarity.LEGENDARY to Formatting.GOLD,
|
||||
Rarity.MYTHIC to Formatting.LIGHT_PURPLE,
|
||||
Rarity.DIVINE to Formatting.AQUA,
|
||||
Rarity.SPECIAL to Formatting.RED,
|
||||
Rarity.VERY_SPECIAL to Formatting.RED,
|
||||
Rarity.SUPREME to Formatting.DARK_RED,
|
||||
).mapValues {
|
||||
private val rarityToColor = Rarity.colourMap.mapValues {
|
||||
val c = Color(it.value.colorValue!!)
|
||||
c.rgb
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
package moe.nea.firmament.features.inventory
|
||||
|
||||
import com.mojang.blaze3d.systems.RenderSystem
|
||||
import java.util.UUID
|
||||
import org.lwjgl.glfw.GLFW
|
||||
import kotlinx.serialization.Serializable
|
||||
@@ -14,6 +13,7 @@ import net.minecraft.screen.GenericContainerScreenHandler
|
||||
import net.minecraft.screen.slot.Slot
|
||||
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.HandledScreenForegroundEvent
|
||||
import moe.nea.firmament.events.HandledScreenKeyPressedEvent
|
||||
@@ -59,6 +59,17 @@ 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 }
|
||||
}
|
||||
|
||||
enum class SlotRenderLinesMode : StringIdentifiable {
|
||||
EVERYTHING,
|
||||
ONLY_BOXES,
|
||||
NOTHING;
|
||||
|
||||
override fun asString(): String {
|
||||
return name
|
||||
}
|
||||
}
|
||||
|
||||
override val config: TConfig
|
||||
@@ -95,7 +106,7 @@ object SlotLocking : FirmamentFeature {
|
||||
if (handler.inventory.size() < 9) return false
|
||||
val sellItem = handler.inventory.getStack(handler.inventory.size() - 5)
|
||||
if (sellItem == null) return false
|
||||
if (sellItem.displayNameAccordingToNbt?.unformattedString == "Sell Item") return true
|
||||
if (sellItem.displayNameAccordingToNbt.unformattedString == "Sell Item") return true
|
||||
val lore = sellItem.loreAccordingToNbt
|
||||
return (lore.lastOrNull() ?: return false).unformattedString == "Click to buyback!"
|
||||
}
|
||||
@@ -104,7 +115,7 @@ object SlotLocking : FirmamentFeature {
|
||||
fun onSalvageProtect(event: IsSlotProtectedEvent) {
|
||||
if (event.slot == null) return
|
||||
if (!event.slot.hasStack()) return
|
||||
if (event.slot.stack.displayNameAccordingToNbt?.unformattedString != "Salvage Items") return
|
||||
if (event.slot.stack.displayNameAccordingToNbt.unformattedString != "Salvage Items") return
|
||||
val inv = event.slot.inventory
|
||||
var anyBlocked = false
|
||||
for (i in 0 until event.slot.index) {
|
||||
@@ -227,23 +238,32 @@ object SlotLocking : FirmamentFeature {
|
||||
val accScreen = event.screen as AccessorHandledScreen
|
||||
val sx = accScreen.x_Firmament
|
||||
val sy = accScreen.y_Firmament
|
||||
boundSlots.entries.forEach {
|
||||
val hotbarSlot = findByIndex(it.key) ?: return@forEach
|
||||
val inventorySlot = findByIndex(it.value) ?: return@forEach
|
||||
for (it in boundSlots.entries) {
|
||||
val hotbarSlot = findByIndex(it.key) ?: continue
|
||||
val inventorySlot = findByIndex(it.value) ?: continue
|
||||
|
||||
val (hotX, hotY) = hotbarSlot.lineCenter()
|
||||
val (invX, invY) = inventorySlot.lineCenter()
|
||||
event.context.drawLine(
|
||||
invX + sx, invY + sy,
|
||||
hotX + sx, hotY + sy,
|
||||
val anyHovered = accScreen.focusedSlot_Firmament === hotbarSlot
|
||||
|| accScreen.focusedSlot_Firmament === inventorySlot
|
||||
if (!anyHovered && TConfig.slotRenderLines == SlotRenderLinesMode.NOTHING)
|
||||
continue
|
||||
val color = if (anyHovered)
|
||||
me.shedaniel.math.Color.ofOpaque(0x00FF00)
|
||||
)
|
||||
else
|
||||
me.shedaniel.math.Color.ofTransparent(0xc0a0f000.toInt())
|
||||
if (TConfig.slotRenderLines == SlotRenderLinesMode.EVERYTHING || anyHovered)
|
||||
event.context.drawLine(
|
||||
invX + sx, invY + sy,
|
||||
hotX + sx, hotY + sy,
|
||||
color
|
||||
)
|
||||
event.context.drawBorder(hotbarSlot.x + sx,
|
||||
hotbarSlot.y + sy,
|
||||
16, 16, 0xFF00FF00u.toInt())
|
||||
16, 16, color.color)
|
||||
event.context.drawBorder(inventorySlot.x + sx,
|
||||
inventorySlot.y + sy,
|
||||
16, 16, 0xFF00FF00u.toInt())
|
||||
16, 16, color.color)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
130
src/main/kotlin/features/inventory/TimerInLore.kt
Normal file
130
src/main/kotlin/features/inventory/TimerInLore.kt
Normal file
@@ -0,0 +1,130 @@
|
||||
package moe.nea.firmament.features.inventory
|
||||
|
||||
import java.time.ZoneId
|
||||
import java.time.ZonedDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.format.DateTimeFormatterBuilder
|
||||
import java.time.format.FormatStyle
|
||||
import java.time.format.TextStyle
|
||||
import java.time.temporal.ChronoField
|
||||
import net.minecraft.text.Text
|
||||
import net.minecraft.util.StringIdentifiable
|
||||
import moe.nea.firmament.annotations.Subscribe
|
||||
import moe.nea.firmament.events.ItemTooltipEvent
|
||||
import moe.nea.firmament.gui.config.ManagedConfig
|
||||
import moe.nea.firmament.util.SBData
|
||||
import moe.nea.firmament.util.aqua
|
||||
import moe.nea.firmament.util.grey
|
||||
import moe.nea.firmament.util.mc.displayNameAccordingToNbt
|
||||
import moe.nea.firmament.util.tr
|
||||
import moe.nea.firmament.util.unformattedString
|
||||
|
||||
object TimerInLore {
|
||||
object TConfig : ManagedConfig("lore-timers", Category.INVENTORY) {
|
||||
val showTimers by toggle("show") { true }
|
||||
val timerFormat by choice("format") { TimerFormat.SOCIALIST }
|
||||
}
|
||||
|
||||
enum class TimerFormat(val formatter: DateTimeFormatter) : StringIdentifiable {
|
||||
RFC(DateTimeFormatter.RFC_1123_DATE_TIME),
|
||||
LOCAL(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)),
|
||||
SOCIALIST(
|
||||
{
|
||||
appendText(ChronoField.DAY_OF_WEEK, TextStyle.SHORT)
|
||||
appendLiteral(" ")
|
||||
appendValue(ChronoField.DAY_OF_MONTH, 2)
|
||||
appendLiteral(".")
|
||||
appendValue(ChronoField.MONTH_OF_YEAR, 2)
|
||||
appendLiteral(".")
|
||||
appendValue(ChronoField.YEAR, 4)
|
||||
appendLiteral(" ")
|
||||
appendValue(ChronoField.HOUR_OF_DAY, 2)
|
||||
appendLiteral(":")
|
||||
appendValue(ChronoField.MINUTE_OF_HOUR, 2)
|
||||
appendLiteral(":")
|
||||
appendValue(ChronoField.SECOND_OF_MINUTE, 2)
|
||||
}),
|
||||
AMERICAN("EEEE, MMM d h:mm a yyyy"),
|
||||
;
|
||||
|
||||
constructor(block: DateTimeFormatterBuilder.() -> Unit)
|
||||
: this(DateTimeFormatterBuilder().also(block).toFormatter())
|
||||
|
||||
constructor(format: String) : this(DateTimeFormatter.ofPattern(format))
|
||||
|
||||
override fun asString(): String {
|
||||
return name
|
||||
}
|
||||
}
|
||||
|
||||
enum class CountdownTypes(
|
||||
val match: String,
|
||||
val label: String, // TODO: convert to a string
|
||||
val isRelative: Boolean = false,
|
||||
) {
|
||||
STARTING("Starting in:", "Starts at"),
|
||||
STARTS("Starts in:", "Starts at"),
|
||||
INTEREST("Interest in:", "Interest at"),
|
||||
UNTILINTEREST("Until interest:", "Interest at"),
|
||||
ENDS("Ends in:", "Ends at"),
|
||||
REMAINING("Remaining:", "Ends at"),
|
||||
DURATION("Duration:", "Finishes at"),
|
||||
TIMELEFT("Time left:", "Ends at"),
|
||||
EVENTTIMELEFT("Event lasts for", "Ends at", isRelative = true),
|
||||
SHENSUCKS("Auction ends in:", "Auction ends at"),
|
||||
ENDS_PET_LEVELING(
|
||||
"Ends:",
|
||||
"Finishes at"
|
||||
),
|
||||
CALENDARDETAILS(" (§e", "Starts at"),
|
||||
COMMUNITYPROJECTS("Contribute again", "Come back at"),
|
||||
CHOCOLATEFACTORY("Next Charge", "Available at"),
|
||||
STONKSAUCTION("Auction ends in", "Ends at"),
|
||||
LIZSTONKREDEMPTION("Resets in:", "Resets at");
|
||||
}
|
||||
|
||||
val regex =
|
||||
"(?i)(?:(?<years>[0-9]+) ?(y|years?) )?(?:(?<days>[0-9]+) ?(d|days?))? ?(?:(?<hours>[0-9]+) ?(h|hours?))? ?(?:(?<minutes>[0-9]+) ?(m|minutes?))? ?(?:(?<seconds>[0-9]+) ?(s|seconds?))?\\b".toRegex()
|
||||
|
||||
@Subscribe
|
||||
fun modifyLore(event: ItemTooltipEvent) {
|
||||
if (!TConfig.showTimers) return
|
||||
var lastTimer: ZonedDateTime? = null
|
||||
for (i in event.lines.indices) {
|
||||
val line = event.lines[i].unformattedString
|
||||
val countdownType = CountdownTypes.entries.find { it.match in line } ?: continue
|
||||
if (countdownType == CountdownTypes.CALENDARDETAILS
|
||||
&& !event.stack.displayNameAccordingToNbt.unformattedString.startsWith("Day ")
|
||||
) continue
|
||||
|
||||
val countdownMatch = regex.findAll(line).filter { it.value.isNotBlank() }.lastOrNull() ?: continue
|
||||
val (years, days, hours, minutes, seconds) =
|
||||
listOf("years", "days", "hours", "minutes", "seconds")
|
||||
.map {
|
||||
countdownMatch.groups[it]?.value?.toLong() ?: 0L
|
||||
}
|
||||
if (years + days + hours + minutes + seconds == 0L) continue
|
||||
var baseLine = ZonedDateTime.now(SBData.hypixelTimeZone)
|
||||
if (countdownType.isRelative) {
|
||||
if (lastTimer == null) {
|
||||
event.lines.add(i + 1,
|
||||
tr("firmament.loretimer.missingrelative",
|
||||
"Found a relative countdown with no baseline (Firmament)").grey())
|
||||
continue
|
||||
}
|
||||
baseLine = lastTimer
|
||||
}
|
||||
val timer =
|
||||
baseLine.plusYears(years).plusDays(days).plusHours(hours).plusMinutes(minutes).plusSeconds(seconds)
|
||||
lastTimer = timer
|
||||
val localTimer = timer.withZoneSameInstant(ZoneId.systemDefault())
|
||||
// TODO: install approximate time stabilization algorithm
|
||||
event.lines.add(i + 1,
|
||||
Text.literal("${countdownType.label}: ")
|
||||
.grey()
|
||||
.append(Text.literal(TConfig.timerFormat.formatter.format(localTimer)).aqua())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<ItemStack>
|
||||
val stacks: List<ItemStack>
|
||||
) {
|
||||
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<VirtualInventory> {
|
||||
const val INVENTORY = "INVENTORY"
|
||||
override val descriptor: SerialDescriptor
|
||||
get() = PrimitiveSerialDescriptor("VirtualInventory", PrimitiveKind.STRING)
|
||||
object Serializer : KSerializer<VirtualInventory> {
|
||||
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())
|
||||
return VirtualInventory(items.map {
|
||||
it as NbtCompound
|
||||
if (it.isEmpty) ItemStack.EMPTY
|
||||
else runCatching {
|
||||
ItemStack.CODEC.parse(NbtOps.INSTANCE, it).orThrow
|
||||
}.getOrElse { 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())
|
||||
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 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())
|
||||
}
|
||||
}
|
||||
fun getOps() = TolerantRegistriesOps(NbtOps.INSTANCE, MC.currentOrDefaultRegistries)
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,6 @@ object PickaxeAbility : FirmamentFeature {
|
||||
val drillFuelBar by toggle("fuel-bar") { true }
|
||||
val blockOnPrivateIsland by choice(
|
||||
"block-on-dynamic",
|
||||
BlockPickaxeAbility.entries,
|
||||
) {
|
||||
BlockPickaxeAbility.ONLY_DESTRUCTIVE
|
||||
}
|
||||
@@ -99,6 +98,7 @@ object PickaxeAbility : FirmamentFeature {
|
||||
@Subscribe
|
||||
fun onPickaxeRightClick(event: UseItemEvent) {
|
||||
if (TConfig.blockOnPrivateIsland == BlockPickaxeAbility.NEVER) return
|
||||
if (SBData.skyblockLocation != SkyBlockIsland.PRIVATE_ISLAND && SBData.skyblockLocation != SkyBlockIsland.GARDEN) return
|
||||
val itemType = ItemType.fromItemStack(event.item)
|
||||
if (itemType !in pickaxeTypes) return
|
||||
val ability = AbilityUtils.getAbilities(event.item)
|
||||
|
||||
124
src/main/kotlin/features/misc/TimerFeature.kt
Normal file
124
src/main/kotlin/features/misc/TimerFeature.kt
Normal file
@@ -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<Timer>()
|
||||
|
||||
@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])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user