aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/kotlin/util')
-rw-r--r--src/main/kotlin/util/Base64Util.kt25
-rw-r--r--src/main/kotlin/util/BazaarPriceStrategy.kt2
-rw-r--r--src/main/kotlin/util/ChromaColourUtil.kt10
-rw-r--r--src/main/kotlin/util/CommonSoundEffects.kt14
-rw-r--r--src/main/kotlin/util/DurabilityBarEvent.kt2
-rw-r--r--src/main/kotlin/util/ErrorUtil.kt25
-rw-r--r--src/main/kotlin/util/FirmFormatters.kt18
-rw-r--r--src/main/kotlin/util/FragmentGuiScreen.kt51
-rw-r--r--src/main/kotlin/util/HoveredItemStack.kt46
-rw-r--r--src/main/kotlin/util/IdentifierSerializer.kt10
-rw-r--r--src/main/kotlin/util/IntUtil.kt12
-rw-r--r--src/main/kotlin/util/LegacyFormattingCode.kt4
-rw-r--r--src/main/kotlin/util/LegacyTagParser.kt54
-rw-r--r--src/main/kotlin/util/LegacyTagWriter.kt103
-rw-r--r--src/main/kotlin/util/LoadResource.kt6
-rw-r--r--src/main/kotlin/util/MC.kt138
-rw-r--r--src/main/kotlin/util/MinecraftDispatcher.kt4
-rw-r--r--src/main/kotlin/util/MoulConfigFragment.kt71
-rw-r--r--src/main/kotlin/util/MoulConfigUtils.kt123
-rw-r--r--src/main/kotlin/util/SBData.kt12
-rw-r--r--src/main/kotlin/util/ScoreboardUtil.kt42
-rw-r--r--src/main/kotlin/util/ScreenUtil.kt10
-rw-r--r--src/main/kotlin/util/SkyBlockIsland.kt3
-rw-r--r--src/main/kotlin/util/SkyblockId.kt230
-rw-r--r--src/main/kotlin/util/StringUtil.kt8
-rw-r--r--src/main/kotlin/util/TemplateUtil.kt3
-rw-r--r--src/main/kotlin/util/TestUtil.kt1
-rw-r--r--src/main/kotlin/util/TimeMark.kt5
-rw-r--r--src/main/kotlin/util/WarpUtil.kt145
-rw-r--r--src/main/kotlin/util/accessors/GetRectangle.kt6
-rw-r--r--src/main/kotlin/util/accessors/castAccessor.kt16
-rw-r--r--src/main/kotlin/util/accessors/chathud.kt6
-rw-r--r--src/main/kotlin/util/asm/AsmAnnotationUtil.kt89
-rw-r--r--src/main/kotlin/util/async/CompletableFutureExt.kt6
-rw-r--r--src/main/kotlin/util/async/input.kt112
-rw-r--r--src/main/kotlin/util/collections/RangeUtil.kt40
-rw-r--r--src/main/kotlin/util/colorconversion.kt8
-rw-r--r--src/main/kotlin/util/customgui/CoordRememberingSlot.kt2
-rw-r--r--src/main/kotlin/util/customgui/CustomGui.kt31
-rw-r--r--src/main/kotlin/util/customgui/HasCustomGui.kt4
-rw-r--r--src/main/kotlin/util/data/Config.kt15
-rw-r--r--src/main/kotlin/util/data/DataHolder.kt63
-rw-r--r--src/main/kotlin/util/data/IDataHolder.kt154
-rw-r--r--src/main/kotlin/util/data/MultiFileDataHolder.kt63
-rw-r--r--src/main/kotlin/util/data/ProfileSpecificDataHolder.kt83
-rw-r--r--src/main/kotlin/util/json/BlockPosSerializer.kt2
-rw-r--r--src/main/kotlin/util/json/CodecSerializer.kt26
-rw-r--r--src/main/kotlin/util/json/DashlessUUIDSerializer.kt7
-rw-r--r--src/main/kotlin/util/json/FirmCodecs.kt4
-rw-r--r--src/main/kotlin/util/json/InstantAsLongSerializer.kt6
-rw-r--r--src/main/kotlin/util/json/KJsonUtils.kt11
-rw-r--r--src/main/kotlin/util/json/jsonConversion.kt65
-rw-r--r--src/main/kotlin/util/math/GChainReconciliation.kt102
-rw-r--r--src/main/kotlin/util/math/Projections.kt46
-rw-r--r--src/main/kotlin/util/mc/ArmorUtil.kt8
-rw-r--r--src/main/kotlin/util/mc/CustomRenderPassHelper.kt161
-rw-r--r--src/main/kotlin/util/mc/FakeInventory.kt24
-rw-r--r--src/main/kotlin/util/mc/FakeSlot.kt12
-rw-r--r--src/main/kotlin/util/mc/FirmamentDataComponentTypes.kt28
-rw-r--r--src/main/kotlin/util/mc/InitLevel.kt25
-rw-r--r--src/main/kotlin/util/mc/IntrospectableItemModelManager.kt4
-rw-r--r--src/main/kotlin/util/mc/InventoryUtil.kt14
-rw-r--r--src/main/kotlin/util/mc/ItemUtil.kt38
-rw-r--r--src/main/kotlin/util/mc/MCTabListAPI.kt96
-rw-r--r--src/main/kotlin/util/mc/NbtItemData.kt24
-rw-r--r--src/main/kotlin/util/mc/NbtPrism.kt85
-rw-r--r--src/main/kotlin/util/mc/NbtUtil.kt15
-rw-r--r--src/main/kotlin/util/mc/PlayerUtil.kt7
-rw-r--r--src/main/kotlin/util/mc/Rectangle.kt11
-rw-r--r--src/main/kotlin/util/mc/SNbtFormatter.kt86
-rw-r--r--src/main/kotlin/util/mc/ScreenUtil.kt16
-rw-r--r--src/main/kotlin/util/mc/SkullItemData.kt106
-rw-r--r--src/main/kotlin/util/mc/SlotUtils.kt52
-rw-r--r--src/main/kotlin/util/mc/TolerantRegistriesOps.kt24
-rw-r--r--src/main/kotlin/util/mc/asFakeServer.kt24
-rw-r--r--src/main/kotlin/util/net/HttpUtil.kt86
-rw-r--r--src/main/kotlin/util/regex.kt7
-rw-r--r--src/main/kotlin/util/render/CustomRenderLayers.kt105
-rw-r--r--src/main/kotlin/util/render/DrawContextExt.kt210
-rw-r--r--src/main/kotlin/util/render/DumpTexture.kt34
-rw-r--r--src/main/kotlin/util/render/FacingThePlayerContext.kt104
-rw-r--r--src/main/kotlin/util/render/FirmamentShaders.kt20
-rw-r--r--src/main/kotlin/util/render/LerpUtils.kt37
-rw-r--r--src/main/kotlin/util/render/MultiSpecialGuiRenderState.kt47
-rw-r--r--src/main/kotlin/util/render/RenderCircleProgress.kt229
-rw-r--r--src/main/kotlin/util/render/RenderInWorldContext.kt352
-rw-r--r--src/main/kotlin/util/render/TintedOverlayTexture.kt21
-rw-r--r--src/main/kotlin/util/render/TranslatedScissors.kt41
-rw-r--r--src/main/kotlin/util/skyblock/AbilityUtils.kt12
-rw-r--r--src/main/kotlin/util/skyblock/DungeonUtil.kt4
-rw-r--r--src/main/kotlin/util/skyblock/ItemType.kt8
-rw-r--r--src/main/kotlin/util/skyblock/PartyUtil.kt210
-rw-r--r--src/main/kotlin/util/skyblock/Rarity.kt34
-rw-r--r--src/main/kotlin/util/skyblock/SBItemUtil.kt4
-rw-r--r--src/main/kotlin/util/skyblock/SackUtil.kt33
-rw-r--r--src/main/kotlin/util/skyblock/ScreenIdentification.kt52
-rw-r--r--src/main/kotlin/util/skyblock/SkyBlockItems.kt11
-rw-r--r--src/main/kotlin/util/skyblock/TabListAPI.kt41
-rw-r--r--src/main/kotlin/util/textutil.kt165
-rw-r--r--src/main/kotlin/util/uuid.kt6
100 files changed, 3413 insertions, 1464 deletions
diff --git a/src/main/kotlin/util/Base64Util.kt b/src/main/kotlin/util/Base64Util.kt
index 44bcdfd..0b7b3ea 100644
--- a/src/main/kotlin/util/Base64Util.kt
+++ b/src/main/kotlin/util/Base64Util.kt
@@ -1,10 +1,23 @@
-
package moe.nea.firmament.util
+import java.util.Base64
+
object Base64Util {
- fun String.padToValidBase64(): String {
- val align = this.length % 4
- if (align == 0) return this
- return this + "=".repeat(4 - align)
- }
+ fun decodeString(str: String): String {
+ return decodeBytes(str).decodeToString()
+ }
+
+ fun decodeBytes(str: String): ByteArray {
+ return Base64.getDecoder().decode(str.padToValidBase64())
+ }
+
+ fun String.padToValidBase64(): String {
+ val align = this.length % 4
+ if (align == 0) return this
+ return this + "=".repeat(4 - align)
+ }
+
+ fun encodeToString(bytes: ByteArray): String {
+ return Base64.getEncoder().encodeToString(bytes)
+ }
}
diff --git a/src/main/kotlin/util/BazaarPriceStrategy.kt b/src/main/kotlin/util/BazaarPriceStrategy.kt
index 002eedb..13b6d95 100644
--- a/src/main/kotlin/util/BazaarPriceStrategy.kt
+++ b/src/main/kotlin/util/BazaarPriceStrategy.kt
@@ -9,7 +9,7 @@ enum class BazaarPriceStrategy {
NPC_SELL;
fun getSellPrice(skyblockId: SkyblockId): Double {
- val bazaarEntry = HypixelStaticData.bazaarData[skyblockId] ?: return 0.0
+ val bazaarEntry = HypixelStaticData.bazaarData[skyblockId.asBazaarStock] ?: return 0.0
return when (this) {
BUY_ORDER -> bazaarEntry.quickStatus.sellPrice
SELL_ORDER -> bazaarEntry.quickStatus.buyPrice
diff --git a/src/main/kotlin/util/ChromaColourUtil.kt b/src/main/kotlin/util/ChromaColourUtil.kt
new file mode 100644
index 0000000..0130326
--- /dev/null
+++ b/src/main/kotlin/util/ChromaColourUtil.kt
@@ -0,0 +1,10 @@
+package moe.nea.firmament.util
+
+import io.github.notenoughupdates.moulconfig.ChromaColour
+import java.awt.Color
+
+fun ChromaColour.getRGBAWithoutAnimation() =
+ Color(ChromaColour.specialToSimpleRGB(toLegacyString()), true)
+
+fun Color.toChromaWithoutAnimation(timeForFullRotationInMillis: Int = 0) =
+ ChromaColour.fromRGB(red, green, blue, timeForFullRotationInMillis, alpha)
diff --git a/src/main/kotlin/util/CommonSoundEffects.kt b/src/main/kotlin/util/CommonSoundEffects.kt
index a97a2cb..a4d7129 100644
--- a/src/main/kotlin/util/CommonSoundEffects.kt
+++ b/src/main/kotlin/util/CommonSoundEffects.kt
@@ -2,18 +2,18 @@
package moe.nea.firmament.util
-import net.minecraft.client.sound.PositionedSoundInstance
-import net.minecraft.sound.SoundEvent
-import net.minecraft.util.Identifier
+import net.minecraft.client.resources.sounds.SimpleSoundInstance
+import net.minecraft.sounds.SoundEvent
+import net.minecraft.resources.ResourceLocation
// TODO: Replace these with custom sound events that just re use the vanilla ogg s
object CommonSoundEffects {
- fun playSound(identifier: Identifier) {
- MC.soundManager.play(PositionedSoundInstance.master(SoundEvent.of(identifier), 1F))
+ fun playSound(identifier: ResourceLocation) {
+ MC.soundManager.play(SimpleSoundInstance.forUI(SoundEvent.createVariableRangeEvent(identifier), 1F))
}
fun playFailure() {
- playSound(Identifier.of("minecraft", "block.anvil.place"))
+ playSound(ResourceLocation.fromNamespaceAndPath("minecraft", "block.anvil.place"))
}
fun playSuccess() {
@@ -21,6 +21,6 @@ object CommonSoundEffects {
}
fun playDing() {
- playSound(Identifier.of("minecraft", "entity.arrow.hit_player"))
+ playSound(ResourceLocation.fromNamespaceAndPath("minecraft", "entity.arrow.hit_player"))
}
}
diff --git a/src/main/kotlin/util/DurabilityBarEvent.kt b/src/main/kotlin/util/DurabilityBarEvent.kt
index 993462c..c2f863f 100644
--- a/src/main/kotlin/util/DurabilityBarEvent.kt
+++ b/src/main/kotlin/util/DurabilityBarEvent.kt
@@ -2,7 +2,7 @@
package moe.nea.firmament.util
import me.shedaniel.math.Color
-import net.minecraft.item.ItemStack
+import net.minecraft.world.item.ItemStack
import moe.nea.firmament.events.FirmamentEvent
import moe.nea.firmament.events.FirmamentEventBus
diff --git a/src/main/kotlin/util/ErrorUtil.kt b/src/main/kotlin/util/ErrorUtil.kt
index 190381d..3db4ecd 100644
--- a/src/main/kotlin/util/ErrorUtil.kt
+++ b/src/main/kotlin/util/ErrorUtil.kt
@@ -29,15 +29,31 @@ object ErrorUtil {
inline fun softError(message: String, exception: Throwable) {
if (aggressiveErrors) throw IllegalStateException(message, exception)
- else Firmament.logger.error(message, exception)
+ else logError(message, exception)
+ }
+
+ fun logError(message: String, exception: Throwable) {
+ Firmament.logger.error(message, exception)
+ }
+ fun logError(message: String) {
+ Firmament.logger.error(message)
}
inline fun softError(message: String) {
if (aggressiveErrors) error(message)
- else Firmament.logger.error(message)
+ else logError(message)
+ }
+
+ fun <T> Result<T>.intoCatch(message: String): Catch<T> {
+ return this.map { Catch.succeed(it) }.getOrElse {
+ softError(message, it)
+ Catch.fail(it)
+ }
}
class Catch<T> private constructor(val value: T?, val exc: Throwable?) {
+ fun orNull(): T? = value
+
inline fun or(block: (exc: Throwable) -> T): T {
contract {
callsInPlace(block, InvocationKind.AT_MOST_ONCE)
@@ -73,4 +89,9 @@ object ErrorUtil {
return nullable
}
+ fun softUserError(string: String) {
+ if (TestUtil.isInTest)
+ error(string)
+ MC.sendChat(tr("firmament.usererror", "Firmament encountered a user caused error: $string"))
+ }
}
diff --git a/src/main/kotlin/util/FirmFormatters.kt b/src/main/kotlin/util/FirmFormatters.kt
index a660f51..c4cffc3 100644
--- a/src/main/kotlin/util/FirmFormatters.kt
+++ b/src/main/kotlin/util/FirmFormatters.kt
@@ -12,8 +12,8 @@ import kotlin.math.absoluteValue
import kotlin.math.roundToInt
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
-import net.minecraft.text.Text
-import net.minecraft.util.math.BlockPos
+import net.minecraft.network.chat.Component
+import net.minecraft.core.BlockPos
object FirmFormatters {
@@ -103,7 +103,7 @@ object FirmFormatters {
return sb.toString()
}
- fun debugPath(path: Path): Text {
+ fun debugPath(path: Path): Component {
if (!path.exists()) {
return tr("firmament.path.missing", "$path (missing)").red()
}
@@ -127,12 +127,16 @@ object FirmFormatters {
fun formatBool(
boolean: Boolean,
trueIsGood: Boolean = true,
- ): Text {
- val text = Text.literal(boolean.toString())
+ ): Component {
+ val text = Component.literal(boolean.toString())
return if (boolean == trueIsGood) text.lime() else text.red()
}
- fun formatPosition(position: BlockPos): Text {
- return Text.literal("x: ${position.x}, y: ${position.y}, z: ${position.z}")
+ fun formatPosition(position: BlockPos): Component {
+ return Component.literal("x: ${position.x}, y: ${position.y}, z: ${position.z}")
+ }
+
+ fun formatPercent(value: Double, decimals: Int = 1): String {
+ return "%.${decimals}f%%".format(value * 100)
}
}
diff --git a/src/main/kotlin/util/FragmentGuiScreen.kt b/src/main/kotlin/util/FragmentGuiScreen.kt
index 5e13d51..8797a31 100644
--- a/src/main/kotlin/util/FragmentGuiScreen.kt
+++ b/src/main/kotlin/util/FragmentGuiScreen.kt
@@ -6,26 +6,25 @@ import io.github.notenoughupdates.moulconfig.gui.GuiContext
import me.shedaniel.math.Dimension
import me.shedaniel.math.Point
import me.shedaniel.math.Rectangle
-import net.minecraft.client.gui.DrawContext
-import net.minecraft.client.gui.screen.Screen
-import net.minecraft.text.Text
+import net.minecraft.client.input.MouseButtonEvent
+import net.minecraft.client.gui.GuiGraphics
+import net.minecraft.client.gui.screens.Screen
+import net.minecraft.client.input.CharacterEvent
+import net.minecraft.client.input.KeyEvent
+import net.minecraft.network.chat.Component
abstract class FragmentGuiScreen(
val dismissOnOutOfBounds: Boolean = true
-) : Screen(Text.literal("")) {
+) : Screen(Component.literal("")) {
var popup: MoulConfigFragment? = null
fun createPopup(context: GuiContext, position: Point) {
popup = MoulConfigFragment(context, position) { popup = null }
}
- override fun render(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) {
- super.render(context, mouseX, mouseY, delta)
- context.matrices.push()
- context.matrices.translate(0f, 0f, 1000f)
- popup?.render(context, mouseX, mouseY, delta)
- context.matrices.pop()
- }
+ fun renderPopup(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
+ popup?.render(context, mouseX, mouseY, delta)
+ }
private inline fun ifPopup(ifYes: (MoulConfigFragment) -> Unit): Boolean {
val p = popup ?: return false
@@ -33,15 +32,15 @@ abstract class FragmentGuiScreen(
return true
}
- override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
+ override fun keyPressed(input: KeyEvent): Boolean {
return ifPopup {
- it.keyPressed(keyCode, scanCode, modifiers)
+ it.keyPressed(input)
}
}
- override fun keyReleased(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
+ override fun keyReleased(input: KeyEvent): Boolean {
return ifPopup {
- it.keyReleased(keyCode, scanCode, modifiers)
+ it.keyReleased(input)
}
}
@@ -49,35 +48,35 @@ abstract class FragmentGuiScreen(
ifPopup { it.mouseMoved(mouseX, mouseY) }
}
- override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean {
+ override fun mouseReleased(click: MouseButtonEvent): Boolean {
return ifPopup {
- it.mouseReleased(mouseX, mouseY, button)
+ it.mouseReleased(click)
}
}
- override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean {
+ override fun mouseDragged(click: MouseButtonEvent, offsetX: Double, offsetY: Double): Boolean {
return ifPopup {
- it.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
+ it.mouseDragged(click, offsetX, offsetY)
}
}
- override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
+ override fun mouseClicked(click: MouseButtonEvent, doubled: Boolean): Boolean {
return ifPopup {
if (!Rectangle(
it.position,
- Dimension(it.context.root.width, it.context.root.height)
- ).contains(Point(mouseX, mouseY))
+ Dimension(it.guiContext.root.width, it.guiContext.root.height)
+ ).contains(Point(click.x, click.y))
&& dismissOnOutOfBounds
) {
popup = null
} else {
- it.mouseClicked(mouseX, mouseY, button)
+ it.mouseClicked(click, doubled)
}
- }|| super.mouseClicked(mouseX, mouseY, button)
+ }|| super.mouseClicked(click, doubled)
}
- override fun charTyped(chr: Char, modifiers: Int): Boolean {
- return ifPopup { it.charTyped(chr, modifiers) }
+ override fun charTyped(input: CharacterEvent): Boolean {
+ return ifPopup { it.charTyped(input) }
}
override fun mouseScrolled(
diff --git a/src/main/kotlin/util/HoveredItemStack.kt b/src/main/kotlin/util/HoveredItemStack.kt
index a2e4ad2..91202dd 100644
--- a/src/main/kotlin/util/HoveredItemStack.kt
+++ b/src/main/kotlin/util/HoveredItemStack.kt
@@ -1,27 +1,51 @@
package moe.nea.firmament.util
import com.google.auto.service.AutoService
-import net.minecraft.client.gui.screen.ingame.HandledScreen
-import net.minecraft.item.ItemStack
+import kotlin.jvm.optionals.getOrNull
+import net.minecraft.client.gui.screens.Screen
+import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen
+import net.minecraft.world.item.ItemStack
+import moe.nea.firmament.api.v1.FirmamentAPI
import moe.nea.firmament.mixins.accessor.AccessorHandledScreen
import moe.nea.firmament.util.compatloader.CompatLoader
-interface HoveredItemStackProvider {
- fun provideHoveredItemStack(screen: HandledScreen<*>): ItemStack?
+interface HoveredItemStackProvider : Comparable<HoveredItemStackProvider> {
+ fun provideHoveredItemStack(screen: Screen): ItemStack?
+ override fun compareTo(other: HoveredItemStackProvider): Int {
+ return compareValues(this.prio, other.prio)
+ }
+
+ val prio: Int get() = 0
- companion object : CompatLoader<HoveredItemStackProvider>(HoveredItemStackProvider::class)
+ companion object : CompatLoader<HoveredItemStackProvider>(HoveredItemStackProvider::class) {
+ val sorted = HoveredItemStackProvider.allValidInstances.sorted()
+ }
}
@AutoService(HoveredItemStackProvider::class)
class VanillaScreenProvider : HoveredItemStackProvider {
- override fun provideHoveredItemStack(screen: HandledScreen<*>): ItemStack? {
- screen as AccessorHandledScreen
- val vanillaSlot = screen.focusedSlot_Firmament?.stack
+
+ override fun provideHoveredItemStack(screen: Screen): ItemStack? {
+ if (screen !is AccessorHandledScreen) return null
+ val vanillaSlot = screen.focusedSlot_Firmament?.item
return vanillaSlot
}
+
+ override val prio: Int
+ get() = -1
+}
+
+@AutoService(HoveredItemStackProvider::class)
+class FirmamentStackScreenProvider : HoveredItemStackProvider {
+ override fun provideHoveredItemStack(screen: Screen): ItemStack? {
+ return FirmamentAPI.getInstance()
+ .hoveredItemWidget
+ .getOrNull()
+ ?.itemStack
+ }
}
-val HandledScreen<*>.focusedItemStack: ItemStack?
+val Screen.focusedItemStack: ItemStack?
get() =
- HoveredItemStackProvider.allValidInstances
- .firstNotNullOfOrNull { it.provideHoveredItemStack(this) }
+ HoveredItemStackProvider.sorted
+ .firstNotNullOfOrNull { it.provideHoveredItemStack(this)?.takeIf { !it.isEmpty } }
diff --git a/src/main/kotlin/util/IdentifierSerializer.kt b/src/main/kotlin/util/IdentifierSerializer.kt
index 65c5b1c..2255255 100644
--- a/src/main/kotlin/util/IdentifierSerializer.kt
+++ b/src/main/kotlin/util/IdentifierSerializer.kt
@@ -8,18 +8,18 @@ import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
-import net.minecraft.util.Identifier
+import net.minecraft.resources.ResourceLocation
-object IdentifierSerializer : KSerializer<Identifier> {
+object IdentifierSerializer : KSerializer<ResourceLocation> {
val delegateSerializer = String.serializer()
override val descriptor: SerialDescriptor
get() = PrimitiveSerialDescriptor("Identifier", PrimitiveKind.STRING)
- override fun deserialize(decoder: Decoder): Identifier {
- return Identifier.of(decoder.decodeSerializableValue(delegateSerializer))
+ override fun deserialize(decoder: Decoder): ResourceLocation {
+ return ResourceLocation.parse(decoder.decodeSerializableValue(delegateSerializer))
}
- override fun serialize(encoder: Encoder, value: Identifier) {
+ override fun serialize(encoder: Encoder, value: ResourceLocation) {
encoder.encodeSerializableValue(delegateSerializer, value.toString())
}
}
diff --git a/src/main/kotlin/util/IntUtil.kt b/src/main/kotlin/util/IntUtil.kt
new file mode 100644
index 0000000..2695906
--- /dev/null
+++ b/src/main/kotlin/util/IntUtil.kt
@@ -0,0 +1,12 @@
+package moe.nea.firmament.util
+
+object IntUtil {
+ data class RGBA(val r: Int, val g: Int, val b: Int, val a: Int)
+
+ fun Int.toRGBA(): RGBA {
+ return RGBA(
+ r = (this shr 16) and 0xFF, g = (this shr 8) and 0xFF, b = this and 0xFF, a = (this shr 24) and 0xFF
+ )
+ }
+
+}
diff --git a/src/main/kotlin/util/LegacyFormattingCode.kt b/src/main/kotlin/util/LegacyFormattingCode.kt
index 1a5d1dd..20574d8 100644
--- a/src/main/kotlin/util/LegacyFormattingCode.kt
+++ b/src/main/kotlin/util/LegacyFormattingCode.kt
@@ -1,6 +1,6 @@
package moe.nea.firmament.util
-import net.minecraft.util.Formatting
+import net.minecraft.ChatFormatting
enum class LegacyFormattingCode(val label: String, val char: Char, val index: Int) {
BLACK("BLACK", '0', 0),
@@ -30,7 +30,7 @@ enum class LegacyFormattingCode(val label: String, val char: Char, val index: In
val byCode = entries.associateBy { it.char }
}
- val modern = Formatting.byCode(char)!!
+ val modern = ChatFormatting.getByCode(char)!!
val formattingCode = "§$char"
diff --git a/src/main/kotlin/util/LegacyTagParser.kt b/src/main/kotlin/util/LegacyTagParser.kt
index 4e08da1..0de4caa 100644
--- a/src/main/kotlin/util/LegacyTagParser.kt
+++ b/src/main/kotlin/util/LegacyTagParser.kt
@@ -2,18 +2,18 @@
package moe.nea.firmament.util
-import java.util.*
-import net.minecraft.nbt.AbstractNbtNumber
-import net.minecraft.nbt.NbtByte
-import net.minecraft.nbt.NbtCompound
-import net.minecraft.nbt.NbtDouble
-import net.minecraft.nbt.NbtElement
-import net.minecraft.nbt.NbtFloat
-import net.minecraft.nbt.NbtInt
-import net.minecraft.nbt.NbtList
-import net.minecraft.nbt.NbtLong
-import net.minecraft.nbt.NbtShort
-import net.minecraft.nbt.NbtString
+import java.util.Stack
+import net.minecraft.nbt.NumericTag
+import net.minecraft.nbt.ByteTag
+import net.minecraft.nbt.CompoundTag
+import net.minecraft.nbt.DoubleTag
+import net.minecraft.nbt.Tag
+import net.minecraft.nbt.FloatTag
+import net.minecraft.nbt.IntTag
+import net.minecraft.nbt.ListTag
+import net.minecraft.nbt.LongTag
+import net.minecraft.nbt.ShortTag
+import net.minecraft.nbt.StringTag
class LegacyTagParser private constructor(string: String) {
data class TagParsingException(val baseString: String, val offset: Int, val mes0: String) :
@@ -93,7 +93,7 @@ class LegacyTagParser private constructor(string: String) {
companion object {
val digitRange = "0123456789-"
- fun parse(string: String): NbtCompound {
+ fun parse(string: String): CompoundTag {
return LegacyTagParser(string).baseTag
}
}
@@ -102,11 +102,11 @@ class LegacyTagParser private constructor(string: String) {
racer.consumeWhile { Character.isWhitespace(it.last()) } // Only check last since other chars are always checked before.
}
- fun parseTag(): NbtCompound {
+ fun parseTag(): CompoundTag {
skipWhitespace()
racer.expect("{", "Expected '{’ at start of tag")
skipWhitespace()
- val tag = NbtCompound()
+ val tag = CompoundTag()
while (!racer.tryConsume("}")) {
skipWhitespace()
val lhs = parseIdentifier()
@@ -121,7 +121,7 @@ class LegacyTagParser private constructor(string: String) {
return tag
}
- private fun parseAny(): NbtElement {
+ private fun parseAny(): Tag {
skipWhitespace()
val nextChar = racer.peekReq(1) ?: racer.error("Expected new object, found EOF")
return when {
@@ -133,11 +133,11 @@ class LegacyTagParser private constructor(string: String) {
}
}
- fun parseList(): NbtList {
+ fun parseList(): ListTag {
skipWhitespace()
racer.expect("[", "Expected '[' at start of tag")
skipWhitespace()
- val list = NbtList()
+ val list = ListTag()
while (!racer.tryConsume("]")) {
skipWhitespace()
racer.pushState()
@@ -183,8 +183,8 @@ class LegacyTagParser private constructor(string: String) {
return sb.toString()
}
- fun parseStringTag(): NbtString {
- return NbtString.of(parseQuotedString())
+ fun parseStringTag(): StringTag {
+ return StringTag.valueOf(parseQuotedString())
}
object Patterns {
@@ -198,7 +198,7 @@ class LegacyTagParser private constructor(string: String) {
val ROUGH_PATTERN = "[-+]?[0-9]*\\.?[0-9]*[dDbBfFlLsS]?".toRegex()
}
- fun parseNumericTag(): AbstractNbtNumber {
+ fun parseNumericTag(): NumericTag {
skipWhitespace()
val textForm = racer.consumeWhile { Patterns.ROUGH_PATTERN.matchEntire(it) != null }
if (textForm.isEmpty()) {
@@ -206,27 +206,27 @@ class LegacyTagParser private constructor(string: String) {
}
val floatMatch = Patterns.FLOAT.matchEntire(textForm)
if (floatMatch != null) {
- return NbtFloat.of(floatMatch.groups[1]!!.value.toFloat())
+ return FloatTag.valueOf(floatMatch.groups[1]!!.value.toFloat())
}
val byteMatch = Patterns.BYTE.matchEntire(textForm)
if (byteMatch != null) {
- return NbtByte.of(byteMatch.groups[1]!!.value.toByte())
+ return ByteTag.valueOf(byteMatch.groups[1]!!.value.toByte())
}
val longMatch = Patterns.LONG.matchEntire(textForm)
if (longMatch != null) {
- return NbtLong.of(longMatch.groups[1]!!.value.toLong())
+ return LongTag.valueOf(longMatch.groups[1]!!.value.toLong())
}
val shortMatch = Patterns.SHORT.matchEntire(textForm)
if (shortMatch != null) {
- return NbtShort.of(shortMatch.groups[1]!!.value.toShort())
+ return ShortTag.valueOf(shortMatch.groups[1]!!.value.toShort())
}
val integerMatch = Patterns.INTEGER.matchEntire(textForm)
if (integerMatch != null) {
- return NbtInt.of(integerMatch.groups[1]!!.value.toInt())
+ return IntTag.valueOf(integerMatch.groups[1]!!.value.toInt())
}
val doubleMatch = Patterns.DOUBLE.matchEntire(textForm) ?: Patterns.DOUBLE_UNTYPED.matchEntire(textForm)
if (doubleMatch != null) {
- return NbtDouble.of(doubleMatch.groups[1]!!.value.toDouble())
+ return DoubleTag.valueOf(doubleMatch.groups[1]!!.value.toDouble())
}
throw IllegalStateException("Could not properly parse numeric tag '$textForm', despite passing rough verification. This is a bug in the LegacyTagParser")
}
diff --git a/src/main/kotlin/util/LegacyTagWriter.kt b/src/main/kotlin/util/LegacyTagWriter.kt
new file mode 100644
index 0000000..eb755c4
--- /dev/null
+++ b/src/main/kotlin/util/LegacyTagWriter.kt
@@ -0,0 +1,103 @@
+package moe.nea.firmament.util
+
+import kotlinx.serialization.json.JsonPrimitive
+import net.minecraft.nbt.CollectionTag
+import net.minecraft.nbt.ByteTag
+import net.minecraft.nbt.CompoundTag
+import net.minecraft.nbt.DoubleTag
+import net.minecraft.nbt.Tag
+import net.minecraft.nbt.EndTag
+import net.minecraft.nbt.FloatTag
+import net.minecraft.nbt.IntTag
+import net.minecraft.nbt.LongTag
+import net.minecraft.nbt.ShortTag
+import net.minecraft.nbt.StringTag
+import moe.nea.firmament.util.mc.SNbtFormatter.Companion.SIMPLE_NAME
+
+class LegacyTagWriter(val compact: Boolean) {
+ companion object {
+ fun stringify(nbt: Tag, compact: Boolean): String {
+ return LegacyTagWriter(compact).also { it.writeElement(nbt) }
+ .stringWriter.toString()
+ }
+
+ fun Tag.toLegacyString(pretty: Boolean = false): String {
+ return stringify(this, !pretty)
+ }
+ }
+
+ val stringWriter = StringBuilder()
+ var indent = 0
+ fun newLine() {
+ if (compact) return
+ stringWriter.append('\n')
+ repeat(indent) {
+ stringWriter.append(" ")
+ }
+ }
+
+ fun writeElement(nbt: Tag) {
+ when (nbt) {
+ is IntTag -> stringWriter.append(nbt.value.toString())
+ is StringTag -> stringWriter.append(escapeString(nbt.value))
+ is FloatTag -> stringWriter.append(nbt.value).append('F')
+ is DoubleTag -> stringWriter.append(nbt.value).append('D')
+ is ByteTag -> stringWriter.append(nbt.value).append('B')
+ is LongTag -> stringWriter.append(nbt.value).append('L')
+ is ShortTag -> stringWriter.append(nbt.value).append('S')
+ is CompoundTag -> writeCompound(nbt)
+ is EndTag -> {}
+ is CollectionTag -> writeArray(nbt)
+ }
+ }
+
+ fun writeArray(nbt: CollectionTag) {
+ stringWriter.append('[')
+ indent++
+ newLine()
+ nbt.forEachIndexed { index, element ->
+ writeName(index.toString())
+ writeElement(element)
+ if (index != nbt.size() - 1) {
+ stringWriter.append(',')
+ newLine()
+ }
+ }
+ indent--
+ if (nbt.size() != 0)
+ newLine()
+ stringWriter.append(']')
+ }
+
+ fun writeCompound(nbt: CompoundTag) {
+ stringWriter.append('{')
+ indent++
+ newLine()
+ val entries = nbt.entrySet().sortedBy { it.key }
+ entries.forEachIndexed { index, it ->
+ writeName(it.key)
+ writeElement(it.value)
+ if (index != entries.lastIndex) {
+ stringWriter.append(',')
+ newLine()
+ }
+ }
+ indent--
+ if (nbt.size() != 0)
+ newLine()
+ stringWriter.append('}')
+ }
+
+ fun escapeString(string: String): String {
+ return JsonPrimitive(string).toString()
+ }
+
+ fun escapeName(key: String): String =
+ if (key.matches(SIMPLE_NAME)) key else escapeString(key)
+
+ fun writeName(key: String) {
+ stringWriter.append(escapeName(key))
+ stringWriter.append(':')
+ if (!compact) stringWriter.append(' ')
+ }
+}
diff --git a/src/main/kotlin/util/LoadResource.kt b/src/main/kotlin/util/LoadResource.kt
index 4bc8704..d3a7ac2 100644
--- a/src/main/kotlin/util/LoadResource.kt
+++ b/src/main/kotlin/util/LoadResource.kt
@@ -4,17 +4,17 @@ package moe.nea.firmament.util
import java.io.InputStream
import kotlin.io.path.inputStream
import kotlin.jvm.optionals.getOrNull
-import net.minecraft.util.Identifier
+import net.minecraft.resources.ResourceLocation
import moe.nea.firmament.repo.RepoDownloadManager
-fun Identifier.openFirmamentResource(): InputStream {
+fun ResourceLocation.openFirmamentResource(): InputStream {
val resource = MC.resourceManager.getResource(this).getOrNull()
if (resource == null) {
if (namespace == "neurepo")
return RepoDownloadManager.repoSavedLocation.resolve(path).inputStream()
error("Could not read resource $this")
}
- return resource.inputStream
+ return resource.open()
}
diff --git a/src/main/kotlin/util/MC.kt b/src/main/kotlin/util/MC.kt
index c1a5e65..e70c382 100644
--- a/src/main/kotlin/util/MC.kt
+++ b/src/main/kotlin/util/MC.kt
@@ -1,39 +1,49 @@
package moe.nea.firmament.util
import io.github.moulberry.repo.data.Coordinate
+import io.github.notenoughupdates.moulconfig.platform.MoulConfigScreenComponent
import java.util.concurrent.ConcurrentLinkedQueue
-import net.minecraft.client.MinecraftClient
-import net.minecraft.client.gui.hud.InGameHud
-import net.minecraft.client.gui.screen.Screen
-import net.minecraft.client.gui.screen.ingame.HandledScreen
-import net.minecraft.client.network.ClientPlayerEntity
-import net.minecraft.client.render.GameRenderer
-import net.minecraft.client.render.WorldRenderer
-import net.minecraft.client.render.item.ItemRenderer
-import net.minecraft.client.world.ClientWorld
-import net.minecraft.entity.Entity
-import net.minecraft.item.Item
-import net.minecraft.item.ItemStack
-import net.minecraft.network.packet.c2s.play.CommandExecutionC2SPacket
-import net.minecraft.registry.BuiltinRegistries
-import net.minecraft.registry.RegistryKeys
-import net.minecraft.registry.RegistryWrapper
-import net.minecraft.resource.ReloadableResourceManagerImpl
-import net.minecraft.text.Text
-import net.minecraft.util.math.BlockPos
-import net.minecraft.world.World
+import kotlin.jvm.optionals.getOrNull
+import net.minecraft.client.Minecraft
+import net.minecraft.client.gui.Gui
+import net.minecraft.client.gui.screens.Screen
+import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen
+import net.minecraft.client.player.LocalPlayer
+import net.minecraft.client.renderer.GameRenderer
+import net.minecraft.client.renderer.LevelRenderer
+import net.minecraft.client.renderer.entity.ItemRenderer
+import net.minecraft.client.multiplayer.ClientLevel
+import net.minecraft.world.entity.Entity
+import net.minecraft.world.item.Item
+import net.minecraft.world.item.ItemStack
+import net.minecraft.nbt.NbtOps
+import net.minecraft.network.protocol.game.ServerboundChatCommandPacket
+import net.minecraft.data.registries.VanillaRegistries
+import net.minecraft.core.Registry
+import net.minecraft.resources.ResourceKey
+import net.minecraft.core.registries.Registries
+import net.minecraft.resources.RegistryOps
+import net.minecraft.core.HolderLookup
+import net.minecraft.server.packs.resources.ReloadableResourceManager
+import net.minecraft.network.chat.Component
+import net.minecraft.resources.ResourceLocation
+import net.minecraft.Util
+import net.minecraft.core.BlockPos
+import net.minecraft.world.level.Level
+import moe.nea.firmament.Firmament
import moe.nea.firmament.events.TickEvent
import moe.nea.firmament.events.WorldReadyEvent
+import moe.nea.firmament.util.mc.TolerantRegistriesOps
object MC {
- private val messageQueue = ConcurrentLinkedQueue<Text>()
+ private val messageQueue = ConcurrentLinkedQueue<Component>()
init {
TickEvent.subscribe("MC:push") {
- if (inGameHud.chatHud != null && world != null)
+ if (inGameHud.chat != null && world != null)
while (true) {
- inGameHud.chatHud.addMessage(messageQueue.poll() ?: break)
+ inGameHud.chat.addMessage(messageQueue.poll() ?: break)
}
while (true) {
(nextTickTodos.poll() ?: break).invoke()
@@ -44,38 +54,42 @@ object MC {
}
}
- fun sendChat(text: Text) {
- if (instance.isOnThread && inGameHud.chatHud != null && world != null)
- inGameHud.chatHud.addMessage(text)
+ fun sendChat(text: Component) {
+ if (TestUtil.isInTest) {
+ Firmament.logger.info("CHAT: ${text.string}")
+ return
+ }
+ if (instance.isSameThread && inGameHud.chat != null && world != null)
+ inGameHud.chat.addMessage(text)
else
messageQueue.add(text)
}
@Deprecated("Use checked method instead", replaceWith = ReplaceWith("sendCommand(command)"))
fun sendServerCommand(command: String) {
- val nh = player?.networkHandler ?: return
- nh.sendPacket(
- CommandExecutionC2SPacket(
+ val nh = player?.connection ?: return
+ nh.send(
+ ServerboundChatCommandPacket(
command,
)
)
}
fun sendServerChat(text: String) {
- player?.networkHandler?.sendChatMessage(text)
+ player?.connection?.sendChat(text)
}
fun sendCommand(command: String) {
// TODO: add a queue to this and sendServerChat
ErrorUtil.softCheck("Server commands have an implied /", !command.startsWith("/"))
- player?.networkHandler?.sendCommand(command)
+ player?.connection?.sendCommand(command)
}
fun onMainThread(block: () -> Unit) {
- if (instance.isOnThread)
+ if (instance.isSameThread)
block()
else
- instance.send(block)
+ instance.schedule(block)
}
private val nextTickTodos = ConcurrentLinkedQueue<() -> Unit>()
@@ -84,42 +98,62 @@ object MC {
}
- inline val resourceManager get() = (instance.resourceManager as ReloadableResourceManagerImpl)
+ inline val resourceManager get() = (instance.resourceManager as ReloadableResourceManager)
inline val itemRenderer: ItemRenderer get() = instance.itemRenderer
- inline val worldRenderer: WorldRenderer get() = instance.worldRenderer
+ inline val worldRenderer: LevelRenderer get() = instance.levelRenderer
inline val gameRenderer: GameRenderer get() = instance.gameRenderer
- inline val networkHandler get() = player?.networkHandler
- inline val instance get() = MinecraftClient.getInstance()
- inline val keyboard get() = instance.keyboard
- inline val interactionManager get() = instance.interactionManager
+ inline val networkHandler get() = player?.connection
+ inline val instance get() = Minecraft.getInstance()
+ inline val keyboard get() = instance.keyboardHandler
+ inline val interactionManager get() = instance.gameMode
inline val textureManager get() = instance.textureManager
inline val options get() = instance.options
- inline val inGameHud: InGameHud get() = instance.inGameHud
- inline val font get() = instance.textRenderer
+ inline val inGameHud: Gui get() = instance.gui
+ inline val font get() = instance.font
inline val soundManager get() = instance.soundManager
- inline val player: ClientPlayerEntity? get() = TestUtil.unlessTesting { instance.player }
+ inline val player: LocalPlayer? get() = TestUtil.unlessTesting { instance.player }
inline val camera: Entity? get() = instance.cameraEntity
- inline val stackInHand: ItemStack get() = player?.inventory?.mainHandStack ?: ItemStack.EMPTY
- inline val guiAtlasManager get() = instance.guiAtlasManager
- inline val world: ClientWorld? get() = TestUtil.unlessTesting { instance.world }
- inline val playerName: String? get() = player?.name?.unformattedString
+ inline val stackInHand: ItemStack get() = player?.mainHandItem ?: ItemStack.EMPTY
+ inline val world: ClientLevel? get() = TestUtil.unlessTesting { instance.level }
+ inline val playerName: String get() = player?.name?.unformattedString ?: MC.instance.user.name
inline var screen: Screen?
- get() = TestUtil.unlessTesting { instance.currentScreen }
+ get() = TestUtil.unlessTesting { instance.screen }
set(value) = instance.setScreen(value)
val screenName get() = screen?.title?.unformattedString?.trim()
- inline val handledScreen: HandledScreen<*>? get() = instance.currentScreen as? HandledScreen<*>
+ inline val handledScreen: AbstractContainerScreen<*>? get() = instance.screen as? AbstractContainerScreen<*>
inline val window get() = instance.window
- inline val currentRegistries: RegistryWrapper.WrapperLookup? get() = world?.registryManager
- val defaultRegistries: RegistryWrapper.WrapperLookup by lazy { BuiltinRegistries.createWrapperLookup() }
+ inline val currentRegistries: HolderLookup.Provider? get() = world?.registryAccess()
+ val defaultRegistries: HolderLookup.Provider by lazy { VanillaRegistries.createLookup() }
+ val defaultRegistryNbtOps by lazy { RegistryOps.create(NbtOps.INSTANCE, defaultRegistries) }
inline val currentOrDefaultRegistries get() = currentRegistries ?: defaultRegistries
- val defaultItems: RegistryWrapper.Impl<Item> by lazy { defaultRegistries.getOrThrow(RegistryKeys.ITEM) }
+ val currentOrDefaultRegistryNbtOps get() = TolerantRegistriesOps(NbtOps.INSTANCE, currentOrDefaultRegistries)
+ val defaultItems: HolderLookup.RegistryLookup<Item> by lazy { defaultRegistries.lookupOrThrow(Registries.ITEM) }
var currentTick = 0
- var lastWorld: World? = null
+ var lastWorld: Level? = null
get() {
field = world ?: field
return field
}
private set
+
+ val currentMoulConfigContext
+ get() = (screen as? MoulConfigScreenComponent)?.guiContext
+
+ fun openUrl(uri: String) {
+ Util.getPlatform().openUri(uri)
+ }
+
+ fun <T> unsafeGetRegistryEntry(registry: ResourceKey<out Registry<T>>, identifier: ResourceLocation) =
+ unsafeGetRegistryEntry(ResourceKey.create(registry, identifier))
+
+
+ fun <T> unsafeGetRegistryEntry(registryKey: ResourceKey<T>): T? {
+ return currentOrDefaultRegistries
+ .lookupOrThrow(registryKey.registryKey())
+ .get(registryKey)
+ .getOrNull()
+ ?.value()
+ }
}
diff --git a/src/main/kotlin/util/MinecraftDispatcher.kt b/src/main/kotlin/util/MinecraftDispatcher.kt
index d1f22a9..3e23f54 100644
--- a/src/main/kotlin/util/MinecraftDispatcher.kt
+++ b/src/main/kotlin/util/MinecraftDispatcher.kt
@@ -3,6 +3,6 @@
package moe.nea.firmament.util
import kotlinx.coroutines.asCoroutineDispatcher
-import net.minecraft.client.MinecraftClient
+import net.minecraft.client.Minecraft
-val MinecraftDispatcher by lazy { MinecraftClient.getInstance().asCoroutineDispatcher() }
+val MinecraftDispatcher by lazy { Minecraft.getInstance().asCoroutineDispatcher() }
diff --git a/src/main/kotlin/util/MoulConfigFragment.kt b/src/main/kotlin/util/MoulConfigFragment.kt
index 36132cd..200b780 100644
--- a/src/main/kotlin/util/MoulConfigFragment.kt
+++ b/src/main/kotlin/util/MoulConfigFragment.kt
@@ -1,44 +1,43 @@
-
-
package moe.nea.firmament.util
-import io.github.notenoughupdates.moulconfig.gui.GuiComponentWrapper
import io.github.notenoughupdates.moulconfig.gui.GuiContext
import io.github.notenoughupdates.moulconfig.gui.GuiImmediateContext
+import io.github.notenoughupdates.moulconfig.platform.MoulConfigScreenComponent
import me.shedaniel.math.Point
-import net.minecraft.client.gui.DrawContext
+import net.minecraft.client.gui.GuiGraphics
+import net.minecraft.network.chat.Component
class MoulConfigFragment(
- context: GuiContext,
- val position: Point,
- val dismiss: () -> Unit
-) : GuiComponentWrapper(context) {
- init {
- this.init(MC.instance, MC.screen!!.width, MC.screen!!.height)
- }
-
- override fun createContext(drawContext: DrawContext?): GuiImmediateContext {
- val oldContext = super.createContext(drawContext)
- return oldContext.translated(
- position.x,
- position.y,
- context.root.width,
- context.root.height,
- )
- }
-
-
- override fun render(drawContext: DrawContext?, i: Int, j: Int, f: Float) {
- val ctx = createContext(drawContext)
- val m = drawContext!!.matrices
- m.push()
- m.translate(position.x.toFloat(), position.y.toFloat(), 0F)
- context.root.render(ctx)
- m.pop()
- ctx.renderContext.doDrawTooltip()
- }
-
- override fun close() {
- dismiss()
- }
+ context: GuiContext,
+ val position: Point,
+ val dismiss: () -> Unit
+) : MoulConfigScreenComponent(Component.empty(), context, null) {
+ init {
+ this.init(MC.instance, MC.screen!!.width, MC.screen!!.height)
+ }
+
+ override fun createContext(drawContext: GuiGraphics?): GuiImmediateContext {
+ val oldContext = super.createContext(drawContext)
+ return oldContext.translated(
+ position.x,
+ position.y,
+ guiContext.root.width,
+ guiContext.root.height,
+ )
+ }
+
+
+ override fun render(drawContext: GuiGraphics, i: Int, j: Int, f: Float) {
+ val ctx = createContext(drawContext)
+ val m = drawContext.pose()
+ m.pushMatrix()
+ m.translate(position.x.toFloat(), position.y.toFloat())
+ guiContext.root.render(ctx)
+ m.popMatrix()
+ ctx.renderContext.renderExtraLayers()
+ }
+
+ override fun onClose() {
+ dismiss()
+ }
}
diff --git a/src/main/kotlin/util/MoulConfigUtils.kt b/src/main/kotlin/util/MoulConfigUtils.kt
index 362a4d9..0c65e69 100644
--- a/src/main/kotlin/util/MoulConfigUtils.kt
+++ b/src/main/kotlin/util/MoulConfigUtils.kt
@@ -4,13 +4,13 @@ import io.github.notenoughupdates.moulconfig.common.IMinecraft
import io.github.notenoughupdates.moulconfig.common.MyResourceLocation
import io.github.notenoughupdates.moulconfig.gui.CloseEventListener
import io.github.notenoughupdates.moulconfig.gui.GuiComponent
-import io.github.notenoughupdates.moulconfig.gui.GuiComponentWrapper
import io.github.notenoughupdates.moulconfig.gui.GuiContext
import io.github.notenoughupdates.moulconfig.gui.GuiImmediateContext
import io.github.notenoughupdates.moulconfig.gui.KeyboardEvent
import io.github.notenoughupdates.moulconfig.gui.MouseEvent
import io.github.notenoughupdates.moulconfig.observer.GetSetter
-import io.github.notenoughupdates.moulconfig.platform.ModernRenderContext
+import io.github.notenoughupdates.moulconfig.platform.MoulConfigRenderContext
+import io.github.notenoughupdates.moulconfig.platform.MoulConfigScreenComponent
import io.github.notenoughupdates.moulconfig.xml.ChildCount
import io.github.notenoughupdates.moulconfig.xml.XMLContext
import io.github.notenoughupdates.moulconfig.xml.XMLGuiLoader
@@ -23,9 +23,11 @@ import me.shedaniel.math.Color
import org.w3c.dom.Element
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 net.minecraft.client.gui.GuiGraphics
+import net.minecraft.client.gui.screens.Screen
+import com.mojang.blaze3d.platform.InputConstants
+import me.shedaniel.math.Rectangle
+import net.minecraft.network.chat.Component
import moe.nea.firmament.gui.BarComponent
import moe.nea.firmament.gui.FirmButtonComponent
import moe.nea.firmament.gui.FirmHoverComponent
@@ -35,6 +37,21 @@ import moe.nea.firmament.gui.TickComponent
import moe.nea.firmament.util.render.isUntranslatedGuiDrawContext
object MoulConfigUtils {
+ @JvmStatic
+ fun main(args: Array<out String>) {
+ generateXSD(File("MoulConfig.xsd"), XMLUniverse.MOULCONFIG_XML_NS)
+ generateXSD(File("MoulConfig.Firmament.xsd"), firmUrl)
+ File("wrapper.xsd").writeText(
+ """
+<?xml version="1.0" encoding="UTF-8" ?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
+ <xs:import namespace="http://notenoughupdates.org/moulconfig" schemaLocation="MoulConfig.xsd"/>
+ <xs:import namespace="http://firmament.nea.moe/moulconfig" schemaLocation="MoulConfig.Firmament.xsd"/>
+</xs:schema>
+ """.trimIndent()
+ )
+ }
+
val firmUrl = "http://firmament.nea.moe/moulconfig"
val universe = XMLUniverse.getDefaultUniverse().also { uni ->
uni.registerMapper(java.awt.Color::class.java) {
@@ -81,9 +98,11 @@ object MoulConfigUtils {
override fun createInstance(context: XMLContext<*>, element: Element): FirmHoverComponent {
return FirmHoverComponent(
context.getChildFragment(element),
- context.getPropertyFromAttribute(element,
- QName("lines"),
- List::class.java) as Supplier<List<String>>,
+ context.getPropertyFromAttribute(
+ element,
+ QName("lines"),
+ List::class.java
+ ) as Supplier<List<String>>,
context.getPropertyFromAttribute(element, QName("delay"), Duration::class.java, 0.6.seconds),
)
}
@@ -179,10 +198,8 @@ object MoulConfigUtils {
uni.registerLoader(object : XMLGuiLoader.Basic<FixedComponent> {
override fun createInstance(context: XMLContext<*>, element: Element): FixedComponent {
return FixedComponent(
- context.getPropertyFromAttribute(element, QName("width"), Int::class.java)
- ?: error("Requires width specified"),
- context.getPropertyFromAttribute(element, QName("height"), Int::class.java)
- ?: error("Requires height specified"),
+ context.getPropertyFromAttribute(element, QName("width"), Int::class.java),
+ context.getPropertyFromAttribute(element, QName("height"), Int::class.java),
context.getChildFragment(element)
)
}
@@ -196,7 +213,7 @@ object MoulConfigUtils {
}
override fun getAttributeNames(): Map<String, Boolean> {
- return mapOf("width" to true, "height" to true)
+ return mapOf("width" to false, "height" to false)
}
})
}
@@ -210,29 +227,21 @@ object MoulConfigUtils {
generator.dumpToFile(file)
}
- @JvmStatic
- fun main(args: Array<out String>) {
- generateXSD(File("MoulConfig.xsd"), XMLUniverse.MOULCONFIG_XML_NS)
- generateXSD(File("MoulConfig.Firmament.xsd"), firmUrl)
- File("wrapper.xsd").writeText("""
-<?xml version="1.0" encoding="UTF-8" ?>
-<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
- <xs:import namespace="http://notenoughupdates.org/moulconfig" schemaLocation="MoulConfig.xsd"/>
- <xs:import namespace="http://firmament.nea.moe/moulconfig" schemaLocation="MoulConfig.Firmament.xsd"/>
-</xs:schema>
- """.trimIndent())
- }
-
- fun loadScreen(name: String, bindTo: Any, parent: Screen?): Screen {
- return object : GuiComponentWrapper(loadGui(name, bindTo)) {
- override fun close() {
- if (context.onBeforeClose() == CloseEventListener.CloseAction.NO_OBJECTIONS_TO_CLOSE) {
- client!!.setScreen(parent)
+ fun wrapScreen(guiContext: GuiContext, parent: Screen?, onClose: () -> Unit = {}): Screen {
+ return object : MoulConfigScreenComponent(Component.empty(), guiContext, null) {
+ override fun onClose() {
+ if (guiContext.onBeforeClose() == CloseEventListener.CloseAction.NO_OBJECTIONS_TO_CLOSE) {
+ minecraft!!.setScreen(parent)
+ onClose()
}
}
}
}
+ fun loadScreen(name: String, bindTo: Any, parent: Screen?): Screen {
+ return wrapScreen(loadGui(name, bindTo), parent)
+ }
+
// TODO: move this utility into moulconfig (also rework guicontext into an interface so i can make this mesh better into vanilla)
fun GuiContext.adopt(element: GuiComponent) = element.foldRecursive(Unit, { comp, unit -> comp.context = this })
@@ -257,12 +266,12 @@ object MoulConfigUtils {
h: Int,
keyboardEvent: KeyboardEvent
): Boolean {
- val immContext = createInPlaceFullContext(null, IMinecraft.instance.mouseX, IMinecraft.instance.mouseY)
+ val immContext = createInPlaceFullContext(null, IMinecraft.INSTANCE.mouseX, IMinecraft.INSTANCE.mouseY)
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
+ && keyboardEvent.pressed && keyboardEvent.keycode == InputConstants.KEY_ESCAPE
) {
component.context.setFocusedElement(null)
}
@@ -284,20 +293,40 @@ object MoulConfigUtils {
return component.mouseEvent(mouseEvent, immContext.translated(x, y, w, h))
}
- fun createInPlaceFullContext(drawContext: DrawContext?, mouseX: Int, mouseY: Int): GuiImmediateContext {
- assert(drawContext?.isUntranslatedGuiDrawContext() != false)
- val context = drawContext?.let(::ModernRenderContext)
- ?: IMinecraft.instance.provideTopLevelRenderContext()
- val immContext = GuiImmediateContext(context,
- 0, 0, 0, 0,
- mouseX, mouseY,
- mouseX, mouseY,
- mouseX.toFloat(),
- mouseY.toFloat())
+ fun <T> createAndTranslateFullContext(
+ drawContext: GuiGraphics?,
+ mouseX: Number, mouseY: Number,
+ rectangle: Rectangle,
+ block: (GuiImmediateContext) -> T
+ ): T {
+ val ctx = createInPlaceFullContext(drawContext, mouseX, mouseY)
+ val pose = drawContext?.pose()
+ pose?.pushMatrix()
+ pose?.translate(rectangle.x.toFloat(), rectangle.y.toFloat())
+ val result = block(ctx.translated(rectangle.x, rectangle.y, rectangle.width, rectangle.height))
+ pose?.popMatrix()
+ return result
+ }
+
+ fun createInPlaceFullContext(drawContext: GuiGraphics?, mouseX: Number, mouseY: Number): GuiImmediateContext {
+ ErrorUtil.softCheck(
+ "created moulconfig context with pre-existing translations.",
+ drawContext?.isUntranslatedGuiDrawContext() != false
+ )
+ val context = drawContext?.let(::MoulConfigRenderContext)
+ ?: IMinecraft.INSTANCE.provideTopLevelRenderContext()
+ val immContext = GuiImmediateContext(
+ context,
+ 0, 0, 0, 0,
+ mouseX.toInt(), mouseY.toInt(),
+ mouseX.toInt(), mouseY.toInt(),
+ mouseX.toFloat(),
+ mouseY.toFloat()
+ )
return immContext
}
- fun DrawContext.drawMCComponentInPlace(
+ fun GuiGraphics.drawMCComponentInPlace(
component: GuiComponent,
x: Int,
y: Int,
@@ -307,10 +336,10 @@ object MoulConfigUtils {
mouseY: Int
) {
val immContext = createInPlaceFullContext(this, mouseX, mouseY)
- matrices.push()
- matrices.translate(x.toFloat(), y.toFloat(), 0F)
+ pose().pushMatrix()
+ pose().translate(x.toFloat(), y.toFloat())
component.render(immContext.translated(x, y, w, h))
- matrices.pop()
+ pose().popMatrix()
}
diff --git a/src/main/kotlin/util/SBData.kt b/src/main/kotlin/util/SBData.kt
index 1a4734c..b3f162b 100644
--- a/src/main/kotlin/util/SBData.kt
+++ b/src/main/kotlin/util/SBData.kt
@@ -18,6 +18,10 @@ object SBData {
"CLICK THIS TO SUGGEST IT IN CHAT [DASHES]",
"CLICK THIS TO SUGGEST IT IN CHAT [NO DASHES]",
)
+
+ val NULL_UUID = UUID(0L, 0L)
+ val profileIdOrNil get() = profileId ?: NULL_UUID
+
var profileId: UUID? = null
get() {
// TODO: allow unfiltered access to this somehow
@@ -37,6 +41,12 @@ object SBData {
*/
val skyblockLocation: SkyBlockIsland? get() = locraw?.skyblockLocation
val hasValidLocraw get() = locraw?.server !in listOf("limbo", null)
+
+ /**
+ * Check if the player is currently on skyblock.
+ *
+ * Nota bene: We don't generally disable features outside of SkyBlock unless they could lead to bans.
+ */
val isOnSkyblock get() = locraw?.gametype == "SKYBLOCK"
var profileIdCommandDebounce = TimeMark.farPast()
fun init() {
@@ -61,7 +71,7 @@ object SBData {
SkyblockServerUpdateEvent.subscribe("SBData:sendProfileId") {
if (!hasReceivedProfile && isOnSkyblock && profileIdCommandDebounce.passedTime() > 10.seconds) {
profileIdCommandDebounce = TimeMark.now()
- MC.sendServerCommand("profileid")
+ MC.sendCommand("profileid")
}
}
AllowChatEvent.subscribe("SBData:hideProfileSuggest") { event ->
diff --git a/src/main/kotlin/util/ScoreboardUtil.kt b/src/main/kotlin/util/ScoreboardUtil.kt
index 0970892..d94eb54 100644
--- a/src/main/kotlin/util/ScoreboardUtil.kt
+++ b/src/main/kotlin/util/ScoreboardUtil.kt
@@ -1,18 +1,18 @@
package moe.nea.firmament.util
import java.util.Optional
-import net.minecraft.client.gui.hud.InGameHud
-import net.minecraft.scoreboard.ScoreboardDisplaySlot
-import net.minecraft.scoreboard.Team
-import net.minecraft.text.StringVisitable
-import net.minecraft.text.Style
-import net.minecraft.text.Text
-import net.minecraft.util.Formatting
+import net.minecraft.client.gui.Gui
+import net.minecraft.world.scores.DisplaySlot
+import net.minecraft.world.scores.PlayerTeam
+import net.minecraft.network.chat.FormattedText
+import net.minecraft.network.chat.Style
+import net.minecraft.network.chat.Component
+import net.minecraft.ChatFormatting
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.TickEvent
object ScoreboardUtil {
- var scoreboardLines: List<Text> = listOf()
+ var scoreboardLines: List<Component> = listOf()
var simplifiedScoreboardLines: List<String> = listOf()
@Subscribe
@@ -21,26 +21,26 @@ object ScoreboardUtil {
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)
+ private fun getScoreboardLinesUncached(): List<Component> {
+ val scoreboard = MC.instance.level?.scoreboard ?: return listOf()
+ val activeObjective = scoreboard.getDisplayObjective(DisplaySlot.SIDEBAR) ?: return listOf()
+ return scoreboard.listPlayerScores(activeObjective)
+ .filter { !it.isHidden() }
+ .sortedWith(Gui.SCORE_DISPLAY_ORDER)
.take(15).map {
- val team = scoreboard.getScoreHolderTeam(it.owner)
- val text = it.name()
- Team.decorateName(team, text)
+ val team = scoreboard.getPlayersTeam(it.owner)
+ val text = it.ownerName()
+ PlayerTeam.formatNameForTeam(team, text)
}
}
}
-fun Text.formattedString(): String {
+fun Component.formattedString(): String {
val sb = StringBuilder()
- visit(StringVisitable.StyledVisitor<Unit> { style, string ->
- val c = Formatting.byName(style.color?.name)
+ visit(FormattedText.StyledContentConsumer<Unit> { style, string ->
+ val c = ChatFormatting.getByName(style.color?.serialize())
if (c != null) {
- sb.append("§${c.code}")
+ sb.append("§${c.char}")
}
if (style.isUnderlined) {
sb.append("§n")
diff --git a/src/main/kotlin/util/ScreenUtil.kt b/src/main/kotlin/util/ScreenUtil.kt
index 99d77fb..98875e0 100644
--- a/src/main/kotlin/util/ScreenUtil.kt
+++ b/src/main/kotlin/util/ScreenUtil.kt
@@ -3,8 +3,8 @@
package moe.nea.firmament.util
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents
-import net.minecraft.client.MinecraftClient
-import net.minecraft.client.gui.screen.Screen
+import net.minecraft.client.Minecraft
+import net.minecraft.client.gui.screens.Screen
import moe.nea.firmament.Firmament
object ScreenUtil {
@@ -12,11 +12,11 @@ object ScreenUtil {
ClientTickEvents.START_CLIENT_TICK.register(::onTick)
}
- private fun onTick(minecraft: MinecraftClient) {
+ private fun onTick(minecraft: Minecraft) {
if (nextOpenedGui != null) {
val p = minecraft.player
- if (p?.currentScreenHandler != null) {
- p.closeHandledScreen()
+ if (p?.containerMenu != null) {
+ p.closeContainer()
}
minecraft.setScreen(nextOpenedGui)
nextOpenedGui = null
diff --git a/src/main/kotlin/util/SkyBlockIsland.kt b/src/main/kotlin/util/SkyBlockIsland.kt
index e7f955a..0fa6376 100644
--- a/src/main/kotlin/util/SkyBlockIsland.kt
+++ b/src/main/kotlin/util/SkyBlockIsland.kt
@@ -41,10 +41,13 @@ private constructor(
val GARDEN = forMode("garden")
val DUNGEON = forMode("dungeon")
val NIL = forMode("_")
+ val GALATEA = forMode("foraging_2")
}
val hasCustomMining
get() = RepoManager.miningData.customMiningAreas[this]?.isSpecialMining ?: false
+ val isModernServer
+ get() = this == GALATEA
val userFriendlyName
get() = RepoManager.neuRepo.constants.islands.areaNames
diff --git a/src/main/kotlin/util/SkyblockId.kt b/src/main/kotlin/util/SkyblockId.kt
index a31255c..0d21559 100644
--- a/src/main/kotlin/util/SkyblockId.kt
+++ b/src/main/kotlin/util/SkyblockId.kt
@@ -6,43 +6,58 @@ import com.mojang.serialization.Codec
import io.github.moulberry.repo.data.NEUIngredient
import io.github.moulberry.repo.data.NEUItem
import io.github.moulberry.repo.data.Rarity
+import java.time.Instant
+import java.time.LocalDateTime
+import java.time.format.DateTimeFormatterBuilder
+import java.time.format.SignStyle
+import java.time.temporal.ChronoField
import java.util.Optional
import java.util.UUID
import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers
import kotlinx.serialization.json.Json
import kotlin.jvm.optionals.getOrNull
-import net.minecraft.component.DataComponentTypes
-import net.minecraft.component.type.NbtComponent
-import net.minecraft.item.ItemStack
-import net.minecraft.item.Items
-import net.minecraft.nbt.NbtCompound
-import net.minecraft.network.RegistryByteBuf
-import net.minecraft.network.codec.PacketCodec
-import net.minecraft.network.codec.PacketCodecs
-import net.minecraft.util.Identifier
+import net.minecraft.core.component.DataComponents
+import net.minecraft.world.item.component.CustomData
+import net.minecraft.world.item.ItemStack
+import net.minecraft.world.item.Items
+import net.minecraft.nbt.CompoundTag
+import net.minecraft.network.RegistryFriendlyByteBuf
+import net.minecraft.network.codec.StreamCodec
+import net.minecraft.network.codec.ByteBufCodecs
+import net.minecraft.resources.ResourceLocation
+import moe.nea.firmament.repo.ExpLadders
+import moe.nea.firmament.repo.ExpensiveItemCacheApi
import moe.nea.firmament.repo.ItemCache.asItemStack
+import moe.nea.firmament.repo.ItemNameLookup
+import moe.nea.firmament.repo.RepoManager
import moe.nea.firmament.repo.set
import moe.nea.firmament.util.collections.WeakCache
import moe.nea.firmament.util.json.DashlessUUIDSerializer
+import moe.nea.firmament.util.mc.displayNameAccordingToNbt
+import moe.nea.firmament.util.mc.loreAccordingToNbt
+import moe.nea.firmament.util.mc.unsafeNbt
+import moe.nea.firmament.util.skyblock.ScreenIdentification
+import moe.nea.firmament.util.skyblock.ScreenType
/**
* A SkyBlock item id, as used by the NEU repo.
- * This is not exactly the format used by HyPixel, but is mostly the same.
- * Usually this id splits an id used by HyPixel into more sub items. For example `PET` becomes `$PET_ID;$PET_RARITY`,
+ * This is not exactly the format used by Hypixel, but is mostly the same.
+ * Usually this id splits an id used by Hypixel into more sub items. For example `PET` becomes `$PET_ID;$PET_RARITY`,
* with those values extracted from other metadata.
*/
@JvmInline
@Serializable
value class SkyblockId(val neuItem: String) : Comparable<SkyblockId> {
val identifier
- get() = Identifier.of("skyblockitem",
- neuItem.lowercase().replace(";", "__")
- .replace(":", "___")
- .replace(illlegalPathRegex) {
- it.value.toCharArray()
- .joinToString("") { "__" + it.code.toString(16).padStart(4, '0') }
- })
+ get() = ResourceLocation.fromNamespaceAndPath(
+ "skyblockitem",
+ neuItem.lowercase().replace(";", "__")
+ .replace(":", "___")
+ .replace(illlegalPathRegex) {
+ it.value.toCharArray()
+ .joinToString("") { "__" + it.code.toString(16).padStart(4, '0') }
+ })
override fun toString(): String {
return neuItem
@@ -53,7 +68,7 @@ value class SkyblockId(val neuItem: String) : Comparable<SkyblockId> {
}
/**
- * A bazaar stock item id, as returned by the HyPixel bazaar api endpoint.
+ * A bazaar stock item id, as returned by the Hypixel bazaar api endpoint.
* These are not equivalent to the in-game ids, or the NEU repo ids, and in fact, do not refer to items, but instead
* to bazaar stocks. The main difference from [SkyblockId]s is concerning enchanted books. There are probably more,
* but for now this holds.
@@ -61,11 +76,10 @@ value class SkyblockId(val neuItem: String) : Comparable<SkyblockId> {
@JvmInline
@Serializable
value class BazaarStock(val bazaarId: String) {
- fun toRepoId(): SkyblockId {
- bazaarEnchantmentRegex.matchEntire(bazaarId)?.let {
- return SkyblockId("${it.groupValues[1]};${it.groupValues[2]}")
+ companion object {
+ fun fromSkyBlockId(skyblockId: SkyblockId): BazaarStock {
+ return BazaarStock(RepoManager.neuRepo.constants.bazaarStocks.getBazaarStockOrDefault(skyblockId.neuItem))
}
- return SkyblockId(bazaarId.replace(":", "-"))
}
}
@@ -77,14 +91,16 @@ value class SkyblockId(val neuItem: String) : Comparable<SkyblockId> {
val PET_NULL: SkyblockId = SkyblockId("null_pet")
private val illlegalPathRegex = "[^a-z0-9_.-/]".toRegex()
val CODEC = Codec.STRING.xmap({ SkyblockId(it) }, { it.neuItem })
- val PACKET_CODEC: PacketCodec<in RegistryByteBuf, SkyblockId> =
- PacketCodecs.STRING.xmap({ SkyblockId(it) }, { it.neuItem })
+ val PACKET_CODEC: StreamCodec<in RegistryFriendlyByteBuf, SkyblockId> =
+ ByteBufCodecs.STRING_UTF8.map({ SkyblockId(it) }, { it.neuItem })
}
}
val NEUItem.skyblockId get() = SkyblockId(skyblockItemId)
val NEUIngredient.skyblockId get() = SkyblockId(itemId)
+val SkyblockId.asBazaarStock get() = SkyblockId.BazaarStock.fromSkyBlockId(this)
+@ExpensiveItemCacheApi
fun NEUItem.guessRecipeId(): String? {
if (!skyblockItemId.contains(";")) return skyblockItemId
val item = this.asItemStack()
@@ -103,40 +119,68 @@ data class HypixelPetInfo(
val exp: Double = 0.0,
val candyUsed: Int = 0,
val uuid: UUID? = null,
- val active: Boolean = false,
+ val active: Boolean? = false,
+ val heldItem: String? = null,
) {
val skyblockId get() = SkyblockId("${type.uppercase()};${tier.ordinal}") // TODO: is this ordinal set up correctly?
+ val level get() = ExpLadders.getExpLadder(type, tier).getPetLevel(exp)
}
private val jsonparser = Json { ignoreUnknownKeys = true }
-var ItemStack.extraAttributes: NbtCompound
+var ItemStack.extraAttributes: CompoundTag
set(value) {
- set(DataComponentTypes.CUSTOM_DATA, NbtComponent.of(value))
+ set(DataComponents.CUSTOM_DATA, CustomData.of(value))
}
get() {
- val customData = get(DataComponentTypes.CUSTOM_DATA) ?: run {
- val component = NbtComponent.of(NbtCompound())
- set(DataComponentTypes.CUSTOM_DATA, component)
+ val customData = get(DataComponents.CUSTOM_DATA) ?: run {
+ val component = CustomData.of(CompoundTag())
+ set(DataComponents.CUSTOM_DATA, component)
component
}
- return customData.nbt
+ return customData.unsafeNbt
}
-fun ItemStack.modifyExtraAttributes(block: (NbtCompound) -> Unit) {
- val baseNbt = get(DataComponentTypes.CUSTOM_DATA)?.copyNbt() ?: NbtCompound()
+fun ItemStack.modifyExtraAttributes(block: (CompoundTag) -> Unit) {
+ val baseNbt = get(DataComponents.CUSTOM_DATA)?.copyTag() ?: CompoundTag()
block(baseNbt)
- set(DataComponentTypes.CUSTOM_DATA, NbtComponent.of(baseNbt))
+ set(DataComponents.CUSTOM_DATA, CustomData.of(baseNbt))
}
-val ItemStack.skyblockUUIDString: String?
- get() = extraAttributes.getString("uuid")?.takeIf { it.isNotBlank() }
+val ItemStack.skyBlockUUIDString: String?
+ get() = extraAttributes.getString("uuid").getOrNull()?.takeIf { it.isNotBlank() }
+
+private val timestampFormat = //"10/11/21 3:39 PM"
+ DateTimeFormatterBuilder().apply {
+ parseCaseInsensitive()
+ appendValue(ChronoField.MONTH_OF_YEAR, 1, 2, SignStyle.NOT_NEGATIVE)
+ appendLiteral("/")
+ appendValue(ChronoField.DAY_OF_MONTH, 1, 2, SignStyle.NOT_NEGATIVE)
+ appendLiteral("/")
+ appendValueReduced(ChronoField.YEAR, 2, 2, 1950)
+ appendLiteral(" ")
+ appendValue(ChronoField.CLOCK_HOUR_OF_AMPM, 1, 2, SignStyle.NEVER)
+ appendLiteral(":")
+ appendValue(ChronoField.MINUTE_OF_HOUR, 2)
+ appendLiteral(" ")
+ appendText(ChronoField.AMPM_OF_DAY)
+ }.toFormatter()
+val ItemStack.timestamp
+ get() =
+ extraAttributes.getLong("timestamp").getOrNull()?.let { Instant.ofEpochMilli(it) }
+ ?: extraAttributes.getString("timestamp").getOrNull()?.let {
+ ErrorUtil.catch("Could not parse timestamp $it") {
+ LocalDateTime.from(timestampFormat.parse(it)).atZone(SBData.hypixelTimeZone)
+ .toInstant()
+ }.orNull()
+ }
val ItemStack.skyblockUUID: UUID?
- get() = skyblockUUIDString?.let { UUID.fromString(it) }
+ get() = skyBlockUUIDString?.let { UUID.fromString(it) }
private val petDataCache = WeakCache.memoize<ItemStack, Optional<HypixelPetInfo>>("PetInfo") {
val jsonString = it.extraAttributes.getString("petInfo")
+ .getOrNull()
if (jsonString.isNullOrBlank()) return@memoize Optional.empty()
ErrorUtil.catch<HypixelPetInfo?>("Could not decode hypixel pet info") {
jsonparser.decodeFromString<HypixelPetInfo>(jsonString)
@@ -145,8 +189,8 @@ private val petDataCache = WeakCache.memoize<ItemStack, Optional<HypixelPetInfo>
}
fun ItemStack.getUpgradeStars(): Int {
- return extraAttributes.getInt("upgrade_level").takeIf { it > 0 }
- ?: extraAttributes.getInt("dungeon_item_level").takeIf { it > 0 }
+ return extraAttributes.getInt("upgrade_level").getOrNull()?.takeIf { it > 0 }
+ ?: extraAttributes.getInt("dungeon_item_level").getOrNull()?.takeIf { it > 0 }
?: 0
}
@@ -155,7 +199,7 @@ fun ItemStack.getUpgradeStars(): Int {
value class ReforgeId(val id: String)
fun ItemStack.getReforgeId(): ReforgeId? {
- return extraAttributes.getString("modifier").takeIf { it.isNotBlank() }?.let(::ReforgeId)
+ return extraAttributes.getString("modifier").getOrNull()?.takeIf { it.isNotBlank() }?.let(::ReforgeId)
}
val ItemStack.petData: HypixelPetInfo?
@@ -167,11 +211,52 @@ fun ItemStack.setSkyBlockId(skyblockId: SkyblockId): ItemStack {
return this
}
+private val STORED_REGEX = "Stored: ($SHORT_NUMBER_FORMAT)/.+".toPattern()
+private val COMPOST_REGEX = "Compost Available: ($SHORT_NUMBER_FORMAT)".toPattern()
+private val GEMSTONE_SACK_REGEX = " Amount: ($SHORT_NUMBER_FORMAT)".toPattern()
+private val AMOUNT_REGEX = ".*(?:Offer amount|Amount|Order amount): ($SHORT_NUMBER_FORMAT)x".toPattern()
+fun ItemStack.getLogicalStackSize(): Long {
+ return loreAccordingToNbt.firstNotNullOfOrNull {
+ val string = it.unformattedString
+ GEMSTONE_SACK_REGEX.useMatch(string) {
+ parseShortNumber(group(1)).toLong()
+ } ?: STORED_REGEX.useMatch(string) {
+ parseShortNumber(group(1)).toLong()
+ } ?: AMOUNT_REGEX.useMatch(string) {
+ parseShortNumber(group(1)).toLong()
+ } ?: COMPOST_REGEX.useMatch(string) {
+ parseShortNumber(group(1)).toLong()
+ }
+ } ?: count.toLong()
+}
+
+val ItemStack.rawSkyBlockId: String? get() = extraAttributes.getString("id").getOrNull()
+
+fun ItemStack.guessContextualSkyBlockId(): SkyblockId? {
+ val screen = MC.screen
+ val screenType = ScreenIdentification.getType(screen)
+ if (screenType == ScreenType.BAZAAR_ANY || screenType == ScreenType.DYE_COMPENDIUM) {
+ val name = displayNameAccordingToNbt.unformattedString
+ .replaceFirst("SELL ", "")
+ .replaceFirst("BUY ", "")
+ if (item == Items.ENCHANTED_BOOK) {
+ return RepoManager.enchantedBookCache.byName[name]
+ }
+ return ItemNameLookup.guessItemByName(name, false)
+ }
+ if (screen != null && (screenType == ScreenType.EXPERIMENTATION_RNG_METER || screenType == ScreenType.SUPER_PAIRS || screenType == ScreenType.ENCHANTMENT_GUIDE)) {
+ val name = displayNameAccordingToNbt.unformattedString
+ return RepoManager.enchantedBookCache.byName[name]
+ ?: ItemNameLookup.guessItemByName(name, false)
+ }
+ return null
+}
+
val ItemStack.skyBlockId: SkyblockId?
get() {
- return when (val id = extraAttributes.getString("id")) {
- "" -> {
- null
+ return when (val id = rawSkyBlockId) {
+ "", null -> {
+ guessContextualSkyBlockId()
}
"PET" -> {
@@ -180,25 +265,68 @@ val ItemStack.skyBlockId: SkyblockId?
"RUNE", "UNIQUE_RUNE" -> {
val runeData = extraAttributes.getCompound("runes")
- val runeKind = runeData.keys.singleOrNull()
+ .getOrNull()
+ val runeKind = runeData?.keySet()?.singleOrNull()
if (runeKind == null) SkyblockId("RUNE")
- else SkyblockId("${runeKind.uppercase()}_RUNE;${runeData.getInt(runeKind)}")
+ else SkyblockId("${runeKind.uppercase()}_RUNE;${runeData.getInt(runeKind).getOrNull()}")
}
"ABICASE" -> {
- SkyblockId("ABICASE_${extraAttributes.getString("model").uppercase()}")
+ SkyblockId("ABICASE_${extraAttributes.getString("model").getOrNull()?.uppercase()}")
}
"ENCHANTED_BOOK" -> {
val enchantmentData = extraAttributes.getCompound("enchantments")
- val enchantName = enchantmentData.keys.singleOrNull()
+ .getOrNull()
+ val enchantName = enchantmentData?.keySet()?.singleOrNull()
if (enchantName == null) SkyblockId("ENCHANTED_BOOK")
- else SkyblockId("${enchantName.uppercase()};${enchantmentData.getInt(enchantName)}")
+ else SkyblockId("${enchantName.uppercase()};${enchantmentData.getInt(enchantName).getOrNull()}")
+ }
+
+ "ATTRIBUTE_SHARD" -> {
+ val attributeData = extraAttributes.getCompound("attributes").getOrNull()
+ val attributeName = attributeData?.keySet()?.singleOrNull()
+ if (attributeName == null) SkyblockId("ATTRIBUTE_SHARD")
+ else SkyblockId(
+ "ATTRIBUTE_SHARD_${attributeName.uppercase()};${
+ attributeData.getInt(attributeName).getOrNull()
+ }"
+ )
+ }
+
+ "POTION" -> {
+ val potionData = extraAttributes.getString("potion").getOrNull()
+ val potionName = extraAttributes.getString("potion_name").getOrNull()
+ val potionLevel = extraAttributes.getInt("potion_level").getOrNull()
+ val potionType = extraAttributes.getString("potion_type").getOrNull()
+ fun String.potionNormalize() = uppercase().replace(" ", "_")
+ when {
+ potionName != null -> SkyblockId("POTION_${potionName.potionNormalize()};$potionLevel")
+ potionData != null -> SkyblockId("POTION_${potionData.potionNormalize()};$potionLevel")
+ potionType != null -> SkyblockId("POTION_${potionType.potionNormalize()}")
+ else -> SkyblockId("WATER_BOTTLE")
+ }
+ }
+
+ "PARTY_HAT_SLOTH", "PARTY_HAT_CRAB", "PARTY_HAT_CRAB_ANIMATED" -> {
+ val partyHatEmoji = extraAttributes.getString("party_hat_emoji").getOrNull()
+ val partyHatYear = extraAttributes.getInt("party_hat_year").getOrNull()
+ val partyHatColor = extraAttributes.getString("party_hat_color").getOrNull()
+ when {
+ partyHatEmoji != null -> SkyblockId("PARTY_HAT_SLOTH_${partyHatEmoji.uppercase()}")
+ partyHatYear == 2022 -> SkyblockId("PARTY_HAT_CRAB_${partyHatColor?.uppercase()}_ANIMATED")
+ else -> SkyblockId("PARTY_HAT_CRAB_${partyHatColor?.uppercase()}")
+ }
+ }
+
+ "BALLOON_HAT_2024", "BALLOON_HAT_2025" -> {
+ val partyHatYear = extraAttributes.getInt("party_hat_year").getOrNull()
+ val partyHatColor = extraAttributes.getString("party_hat_color").getOrNull()
+ SkyblockId("BALLOON_HAT_${partyHatYear}_${partyHatColor?.uppercase()}")
}
- // TODO: PARTY_HAT_CRAB{,_ANIMATED,_SLOTH},POTION
else -> {
- SkyblockId(id)
+ SkyblockId(id.replace(":", "-"))
}
}
}
diff --git a/src/main/kotlin/util/StringUtil.kt b/src/main/kotlin/util/StringUtil.kt
index 68e161a..50c5367 100644
--- a/src/main/kotlin/util/StringUtil.kt
+++ b/src/main/kotlin/util/StringUtil.kt
@@ -5,10 +5,18 @@ object StringUtil {
return splitToSequence(" ") // TODO: better boundaries
}
+ fun String.camelWords(): Sequence<String> {
+ return splitToSequence(camelWordStart)
+ }
+
+ private val camelWordStart = Regex("((?<=[a-z])(?=[A-Z]))| ")
+
fun parseIntWithComma(string: String): Int {
return string.replace(",", "").toInt()
}
+ fun String.title() = replaceFirstChar { it.titlecase() }
+
fun Iterable<String>.unwords() = joinToString(" ")
fun nextLexicographicStringOfSameLength(string: String): String {
val next = StringBuilder(string)
diff --git a/src/main/kotlin/util/TemplateUtil.kt b/src/main/kotlin/util/TemplateUtil.kt
index f4ff37c..44d9ccd 100644
--- a/src/main/kotlin/util/TemplateUtil.kt
+++ b/src/main/kotlin/util/TemplateUtil.kt
@@ -2,10 +2,9 @@
package moe.nea.firmament.util
-import java.util.*
+import java.util.Base64
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.SerializationStrategy
-import kotlinx.serialization.json.Json
import kotlinx.serialization.serializer
import moe.nea.firmament.Firmament
diff --git a/src/main/kotlin/util/TestUtil.kt b/src/main/kotlin/util/TestUtil.kt
index 45e3dde..da8ba38 100644
--- a/src/main/kotlin/util/TestUtil.kt
+++ b/src/main/kotlin/util/TestUtil.kt
@@ -2,6 +2,7 @@ package moe.nea.firmament.util
object TestUtil {
inline fun <T> unlessTesting(block: () -> T): T? = if (isInTest) null else block()
+ @JvmField
val isInTest =
Thread.currentThread().stackTrace.any {
it.className.startsWith("org.junit.") || it.className.startsWith("io.kotest.")
diff --git a/src/main/kotlin/util/TimeMark.kt b/src/main/kotlin/util/TimeMark.kt
index 4a076ac..112a727 100644
--- a/src/main/kotlin/util/TimeMark.kt
+++ b/src/main/kotlin/util/TimeMark.kt
@@ -2,6 +2,7 @@ package moe.nea.firmament.util
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.DurationUnit
class TimeMark private constructor(private val timeMark: Long) : Comparable<TimeMark> {
fun passedTime() =
@@ -50,3 +51,7 @@ class TimeMark private constructor(private val timeMark: Long) : Comparable<Time
return this.timeMark.compareTo(other.timeMark)
}
}
+
+fun Duration.toTicks(): Long {
+ return toLong(DurationUnit.MILLISECONDS) / 50
+}
diff --git a/src/main/kotlin/util/WarpUtil.kt b/src/main/kotlin/util/WarpUtil.kt
index f733af7..3008592 100644
--- a/src/main/kotlin/util/WarpUtil.kt
+++ b/src/main/kotlin/util/WarpUtil.kt
@@ -6,91 +6,94 @@ import kotlinx.serialization.Serializable
import kotlinx.serialization.serializer
import kotlin.math.sqrt
import kotlin.time.Duration.Companion.seconds
-import net.minecraft.text.Text
-import net.minecraft.util.math.Position
+import net.minecraft.network.chat.Component
+import net.minecraft.core.Position
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.commands.thenExecute
import moe.nea.firmament.events.CommandEvent
import moe.nea.firmament.events.ProcessChatEvent
import moe.nea.firmament.repo.RepoManager
+import moe.nea.firmament.util.data.Config
import moe.nea.firmament.util.data.ProfileSpecificDataHolder
object WarpUtil {
- val warps: Sequence<Islands.Warp> get() = RepoManager.neuRepo.constants.islands.warps
- .asSequence()
- .filter { it.warp !in ignoredWarps }
+ val warps: Sequence<Islands.Warp>
+ get() = RepoManager.neuRepo.constants.islands.warps
+ .asSequence()
+ .filter { it.warp !in ignoredWarps }
- val ignoredWarps = setOf("carnival", "")
+ val ignoredWarps = setOf("carnival", "")
- @Serializable
- data class Data(
- val excludedWarps: MutableSet<String> = mutableSetOf(),
- )
+ @Serializable
+ data class Data(
+ val excludedWarps: MutableSet<String> = mutableSetOf(),
+ )
- object DConfig : ProfileSpecificDataHolder<Data>(serializer(), "warp-util", ::Data)
+ @Config
+ object DConfig : ProfileSpecificDataHolder<Data>(serializer(), "warp-util", ::Data)
- private var lastAttemptedWarp = ""
- private var lastWarpAttempt = TimeMark.farPast()
- fun findNearestWarp(island: SkyBlockIsland, pos: Position): Islands.Warp? {
- return warps.asSequence().filter { it.mode == island.locrawMode }.minByOrNull {
- if (DConfig.data?.excludedWarps?.contains(it.warp) == true) {
- return@minByOrNull Double.MAX_VALUE
- } else {
- return@minByOrNull squaredDist(pos, it)
- }
- }
- }
+ private var lastAttemptedWarp = ""
+ private var lastWarpAttempt = TimeMark.farPast()
+ fun findNearestWarp(island: SkyBlockIsland, pos: Position): Islands.Warp? {
+ return warps.asSequence().filter { it.mode == island.locrawMode }.minByOrNull {
+ if (DConfig.data?.excludedWarps?.contains(it.warp) == true) {
+ return@minByOrNull Double.MAX_VALUE
+ } else {
+ return@minByOrNull squaredDist(pos, it)
+ }
+ }
+ }
- private fun squaredDist(pos: Position, warp: Warp): Double {
- val dx = pos.x - warp.x
- val dy = pos.y - warp.y
- val dz = pos.z - warp.z
- return dx * dx + dy * dy + dz * dz
- }
+ private fun squaredDist(pos: Position, warp: Warp): Double {
+ val dx = pos.x() - warp.x
+ val dy = pos.y() - warp.y
+ val dz = pos.z() - warp.z
+ return dx * dx + dy * dy + dz * dz
+ }
- fun teleportToNearestWarp(island: SkyBlockIsland, pos: Position) {
- val nearestWarp = findNearestWarp(island, pos)
- if (nearestWarp == null) {
- MC.sendChat(Text.translatable("firmament.warp-util.no-warp-found", island.userFriendlyName))
- return
- }
- if (island == SBData.skyblockLocation
- && sqrt(squaredDist(pos, nearestWarp)) > 1.1 * sqrt(squaredDist((MC.player ?: return).pos, nearestWarp))
- ) {
- MC.sendChat(Text.translatable("firmament.warp-util.already-close", nearestWarp.warp))
- return
- }
- MC.sendChat(Text.translatable("firmament.warp-util.attempting-to-warp", nearestWarp.warp))
- lastWarpAttempt = TimeMark.now()
- lastAttemptedWarp = nearestWarp.warp
- MC.sendServerCommand("warp ${nearestWarp.warp}")
- }
+ fun teleportToNearestWarp(island: SkyBlockIsland, pos: Position) {
+ val nearestWarp = findNearestWarp(island, pos)
+ if (nearestWarp == null) {
+ MC.sendChat(Component.translatable("firmament.warp-util.no-warp-found", island.userFriendlyName))
+ return
+ }
+ if (island == SBData.skyblockLocation
+ && sqrt(squaredDist(pos, nearestWarp)) > 1.1 * sqrt(squaredDist((MC.player ?: return).position, nearestWarp))
+ ) {
+ MC.sendChat(Component.translatable("firmament.warp-util.already-close", nearestWarp.warp))
+ return
+ }
+ MC.sendChat(Component.translatable("firmament.warp-util.attempting-to-warp", nearestWarp.warp))
+ lastWarpAttempt = TimeMark.now()
+ lastAttemptedWarp = nearestWarp.warp
+ MC.sendCommand("warp ${nearestWarp.warp}")
+ }
- @Subscribe
- fun clearUnlockedWarpsCommand(event: CommandEvent.SubCommand) {
- event.subcommand("clearwarps") {
- thenExecute {
- DConfig.data?.excludedWarps?.clear()
- DConfig.markDirty()
- source.sendFeedback(Text.translatable("firmament.warp-util.clear-excluded"))
- }
- }
- }
+ @Subscribe
+ fun clearUnlockedWarpsCommand(event: CommandEvent.SubCommand) {
+ event.subcommand("clearwarps") {
+ thenExecute {
+ DConfig.data?.excludedWarps?.clear()
+ DConfig.markDirty()
+ source.sendFeedback(Component.translatable("firmament.warp-util.clear-excluded"))
+ }
+ }
+ }
- init {
- ProcessChatEvent.subscribe("WarpUtil:processChat") {
- if (it.unformattedString == "You haven't unlocked this fast travel destination!"
- && lastWarpAttempt.passedTime() < 2.seconds
- ) {
- DConfig.data?.excludedWarps?.add(lastAttemptedWarp)
- DConfig.markDirty()
- MC.sendChat(Text.stringifiedTranslatable("firmament.warp-util.mark-excluded", lastAttemptedWarp))
- lastWarpAttempt = TimeMark.farPast()
- }
- if (it.unformattedString.startsWith("You may now fast travel to")) {
- DConfig.data?.excludedWarps?.clear()
- DConfig.markDirty()
- }
- }
- }
+ init {
+ ProcessChatEvent.subscribe("WarpUtil:processChat") {
+ if (it.unformattedString == "You haven't unlocked this fast travel destination!"
+ && lastWarpAttempt.passedTime() < 2.seconds
+ ) {
+ DConfig.data?.excludedWarps?.add(lastAttemptedWarp)
+ DConfig.markDirty()
+ MC.sendChat(Component.translatableEscape("firmament.warp-util.mark-excluded", lastAttemptedWarp))
+ lastWarpAttempt = TimeMark.farPast()
+ }
+ if (it.unformattedString.startsWith("You may now fast travel to")) {
+ DConfig.data?.excludedWarps?.clear()
+ DConfig.markDirty()
+ }
+ }
+ }
}
diff --git a/src/main/kotlin/util/accessors/GetRectangle.kt b/src/main/kotlin/util/accessors/GetRectangle.kt
index 37acfd9..109de94 100644
--- a/src/main/kotlin/util/accessors/GetRectangle.kt
+++ b/src/main/kotlin/util/accessors/GetRectangle.kt
@@ -3,11 +3,11 @@
package moe.nea.firmament.util.accessors
import me.shedaniel.math.Rectangle
+import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen
import moe.nea.firmament.mixins.accessor.AccessorHandledScreen
-import net.minecraft.client.gui.screen.ingame.HandledScreen
-fun HandledScreen<*>.getRectangle(): Rectangle {
- this as AccessorHandledScreen
+fun AbstractContainerScreen<*>.getProperRectangle(): Rectangle {
+ this.castAccessor()
return Rectangle(
getX_Firmament(),
getY_Firmament(),
diff --git a/src/main/kotlin/util/accessors/castAccessor.kt b/src/main/kotlin/util/accessors/castAccessor.kt
new file mode 100644
index 0000000..0ac85a2
--- /dev/null
+++ b/src/main/kotlin/util/accessors/castAccessor.kt
@@ -0,0 +1,16 @@
+@file:OptIn(ExperimentalContracts::class)
+
+package moe.nea.firmament.util.accessors
+
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.contract
+import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen
+import moe.nea.firmament.mixins.accessor.AccessorHandledScreen
+
+
+inline fun AbstractContainerScreen<*>.castAccessor(): AccessorHandledScreen {
+ contract {
+ returns() implies (this@castAccessor is AccessorHandledScreen)
+ }
+ return this as AccessorHandledScreen
+}
diff --git a/src/main/kotlin/util/accessors/chathud.kt b/src/main/kotlin/util/accessors/chathud.kt
index effac7d..7935ad4 100644
--- a/src/main/kotlin/util/accessors/chathud.kt
+++ b/src/main/kotlin/util/accessors/chathud.kt
@@ -1,8 +1,8 @@
package moe.nea.firmament.util.accessors
-import net.minecraft.client.gui.hud.ChatHud
-import net.minecraft.client.gui.hud.ChatHudLine
+import net.minecraft.client.gui.components.ChatComponent
+import net.minecraft.client.GuiMessage
import moe.nea.firmament.mixins.accessor.AccessorChatHud
-val ChatHud.messages: MutableList<ChatHudLine>
+val ChatComponent.messages: MutableList<GuiMessage>
get() = (this as AccessorChatHud).messages_firmament
diff --git a/src/main/kotlin/util/asm/AsmAnnotationUtil.kt b/src/main/kotlin/util/asm/AsmAnnotationUtil.kt
new file mode 100644
index 0000000..fb0e92c
--- /dev/null
+++ b/src/main/kotlin/util/asm/AsmAnnotationUtil.kt
@@ -0,0 +1,89 @@
+package moe.nea.firmament.util.asm
+
+import com.google.common.base.Defaults
+import java.lang.reflect.InvocationHandler
+import java.lang.reflect.Method
+import java.lang.reflect.Proxy
+import org.objectweb.asm.Type
+import org.objectweb.asm.tree.AnnotationNode
+
+object AsmAnnotationUtil {
+ class AnnotationProxy(
+ val originalType: Class<out Annotation>,
+ val annotationNode: AnnotationNode,
+ ) : InvocationHandler {
+ val offsets = annotationNode.values.withIndex()
+ .chunked(2)
+ .map { it.first() }
+ .associate { (idx, value) -> value as String to idx + 1 }
+
+ fun nestArrayType(depth: Int, comp: Class<*>): Class<*> =
+ if (depth == 0) comp
+ else java.lang.reflect.Array.newInstance(nestArrayType(depth - 1, comp), 0).javaClass
+
+ fun unmap(
+ value: Any?,
+ comp: Class<*>,
+ depth: Int,
+ ): Any? {
+ value ?: return null
+ if (depth > 0)
+ return ((value as List<Any>)
+ .map { unmap(it, comp, depth - 1) } as java.util.List<Any>)
+ .toArray(java.lang.reflect.Array.newInstance(nestArrayType(depth - 1, comp), 0) as Array<*>)
+ if (comp.isEnum) {
+ comp as Class<out Enum<*>>
+ when (value) {
+ is String -> return java.lang.Enum.valueOf(comp, value)
+ is List<*> -> return java.lang.Enum.valueOf(comp, value[1] as String)
+ else -> error("Unknown enum variant $value for $comp")
+ }
+ }
+ when (value) {
+ is Type -> return Class.forName(value.className)
+ is AnnotationNode -> return createProxy(comp as Class<out Annotation>, value)
+ is String, is Boolean, is Byte, is Double, is Int, is Float, is Long, is Short, is Char -> return value
+ }
+ error("Unknown enum variant $value for $comp")
+ }
+
+ fun defaultFor(fullType: Class<*>): Any? {
+ if (fullType.isArray) return java.lang.reflect.Array.newInstance(fullType.componentType, 0)
+ if (fullType.isPrimitive) {
+ return Defaults.defaultValue(fullType)
+ }
+ if (fullType == String::class.java)
+ return ""
+ return null
+ }
+
+ override fun invoke(
+ proxy: Any,
+ method: Method,
+ args: Array<out Any?>?
+ ): Any? {
+ val name = method.name
+ val ret = method.returnType
+ val retU = generateSequence(ret) { if (it.isArray) it.componentType else null }
+ .toList()
+ val arrayDepth = retU.size - 1
+ val componentType = retU.last()
+
+ val off = offsets[name]
+ if (off == null) {
+ return defaultFor(ret)
+ }
+ return unmap(annotationNode.values[off], componentType, arrayDepth)
+ }
+ }
+
+ fun <T : Annotation> createProxy(
+ annotationClass: Class<T>,
+ annotationNode: AnnotationNode
+ ): T {
+ require(Type.getType(annotationClass) == Type.getType(annotationNode.desc))
+ return Proxy.newProxyInstance(javaClass.classLoader,
+ arrayOf(annotationClass),
+ AnnotationProxy(annotationClass, annotationNode)) as T
+ }
+}
diff --git a/src/main/kotlin/util/async/CompletableFutureExt.kt b/src/main/kotlin/util/async/CompletableFutureExt.kt
new file mode 100644
index 0000000..5476371
--- /dev/null
+++ b/src/main/kotlin/util/async/CompletableFutureExt.kt
@@ -0,0 +1,6 @@
+package moe.nea.firmament.util.async
+
+import java.util.concurrent.CompletableFuture
+
+
+fun CompletableFuture<*>.discard(): CompletableFuture<Void?> = thenRun { }
diff --git a/src/main/kotlin/util/async/input.kt b/src/main/kotlin/util/async/input.kt
index f22c595..65479e9 100644
--- a/src/main/kotlin/util/async/input.kt
+++ b/src/main/kotlin/util/async/input.kt
@@ -1,47 +1,89 @@
-
-
package moe.nea.firmament.util.async
+import io.github.notenoughupdates.moulconfig.gui.GuiContext
+import io.github.notenoughupdates.moulconfig.gui.component.CenterComponent
+import io.github.notenoughupdates.moulconfig.gui.component.ColumnComponent
+import io.github.notenoughupdates.moulconfig.gui.component.PanelComponent
+import io.github.notenoughupdates.moulconfig.gui.component.TextComponent
+import io.github.notenoughupdates.moulconfig.gui.component.TextFieldComponent
+import io.github.notenoughupdates.moulconfig.observer.GetSetter
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.resume
+import net.minecraft.client.gui.screens.Screen
import moe.nea.firmament.events.HandledScreenKeyPressedEvent
-import moe.nea.firmament.keybindings.IKeyBinding
+import moe.nea.firmament.gui.FirmButtonComponent
+import moe.nea.firmament.keybindings.SavedKeyBinding
+import moe.nea.firmament.util.MC
+import moe.nea.firmament.util.MoulConfigUtils
+import moe.nea.firmament.util.ScreenUtil
private object InputHandler {
- data class KeyInputContinuation(val keybind: IKeyBinding, val onContinue: () -> Unit)
-
- private val activeContinuations = mutableListOf<KeyInputContinuation>()
-
- fun registerContinuation(keyInputContinuation: KeyInputContinuation): () -> Unit {
- synchronized(InputHandler) {
- activeContinuations.add(keyInputContinuation)
- }
- return {
- synchronized(this) {
- activeContinuations.remove(keyInputContinuation)
- }
- }
- }
-
- init {
- HandledScreenKeyPressedEvent.subscribe("Input:resumeAfterInput") { event ->
- synchronized(InputHandler) {
- val toRemove = activeContinuations.filter {
- event.matches(it.keybind)
- }
- toRemove.forEach { it.onContinue() }
- activeContinuations.removeAll(toRemove)
- }
- }
- }
+ data class KeyInputContinuation(val keybind: SavedKeyBinding, val onContinue: () -> Unit)
+
+ private val activeContinuations = mutableListOf<KeyInputContinuation>()
+
+ fun registerContinuation(keyInputContinuation: KeyInputContinuation): () -> Unit {
+ synchronized(InputHandler) {
+ activeContinuations.add(keyInputContinuation)
+ }
+ return {
+ synchronized(this) {
+ activeContinuations.remove(keyInputContinuation)
+ }
+ }
+ }
+
+ init {
+ HandledScreenKeyPressedEvent.subscribe("Input:resumeAfterInput") { event ->
+ synchronized(InputHandler) {
+ val toRemove = activeContinuations.filter {
+ event.matches(it.keybind)
+ }
+ toRemove.forEach { it.onContinue() }
+ activeContinuations.removeAll(toRemove)
+ }
+ }
+ }
}
-suspend fun waitForInput(keybind: IKeyBinding): Unit = suspendCancellableCoroutine { cont ->
- val unregister =
- InputHandler.registerContinuation(InputHandler.KeyInputContinuation(keybind) { cont.resume(Unit) })
- cont.invokeOnCancellation {
- unregister()
- }
+suspend fun waitForInput(keybind: SavedKeyBinding): Unit = suspendCancellableCoroutine { cont ->
+ val unregister =
+ InputHandler.registerContinuation(InputHandler.KeyInputContinuation(keybind) { cont.resume(Unit) })
+ cont.invokeOnCancellation {
+ unregister()
+ }
}
+fun createPromptScreenGuiComponent(suggestion: String, prompt: String, action: Runnable) = (run {
+ val text = GetSetter.floating(suggestion)
+ GuiContext(
+ CenterComponent(
+ PanelComponent(
+ ColumnComponent(
+ TextFieldComponent(text, 120),
+ FirmButtonComponent(TextComponent(prompt), action = action)
+ )
+ )
+ )
+ ) to text
+})
+
+suspend fun waitForTextInput(suggestion: String, prompt: String) =
+ suspendCancellableCoroutine<String> { cont ->
+ lateinit var screen: Screen
+ lateinit var text: GetSetter<String>
+ val action = {
+ if (MC.screen === screen)
+ MC.screen = null
+ // TODO: should this exit
+ cont.resume(text.get())
+ }
+ val (gui, text_) = createPromptScreenGuiComponent(suggestion, prompt, action)
+ text = text_
+ screen = MoulConfigUtils.wrapScreen(gui, null, onClose = action)
+ ScreenUtil.setScreenLater(screen)
+ cont.invokeOnCancellation {
+ action()
+ }
+ }
diff --git a/src/main/kotlin/util/collections/RangeUtil.kt b/src/main/kotlin/util/collections/RangeUtil.kt
new file mode 100644
index 0000000..a7029ac
--- /dev/null
+++ b/src/main/kotlin/util/collections/RangeUtil.kt
@@ -0,0 +1,40 @@
+package moe.nea.firmament.util.collections
+
+import kotlin.math.floor
+
+val ClosedFloatingPointRange<Float>.centre get() = (endInclusive + start) / 2
+
+fun ClosedFloatingPointRange<Float>.nonNegligibleSubSectionsAlignedWith(
+ interval: Float
+): Iterable<Float> {
+ require(interval.isFinite())
+ val range = this
+ return object : Iterable<Float> {
+ override fun iterator(): Iterator<Float> {
+ return object : FloatIterator() {
+ var polledValue: Float = range.start
+ var lastValue: Float = polledValue
+
+ override fun nextFloat(): Float {
+ if (!hasNext()) throw NoSuchElementException()
+ lastValue = polledValue
+ polledValue = Float.NaN
+ return lastValue
+ }
+
+ override fun hasNext(): Boolean {
+ if (!polledValue.isNaN()) {
+ return true
+ }
+ if (lastValue == range.endInclusive)
+ return false
+ polledValue = (floor(lastValue / interval) + 1) * interval
+ if (polledValue > range.endInclusive) {
+ polledValue = range.endInclusive
+ }
+ return true
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/kotlin/util/colorconversion.kt b/src/main/kotlin/util/colorconversion.kt
index d7a5dad..758e354 100644
--- a/src/main/kotlin/util/colorconversion.kt
+++ b/src/main/kotlin/util/colorconversion.kt
@@ -2,12 +2,12 @@
package moe.nea.firmament.util
-import net.minecraft.text.TextColor
-import net.minecraft.util.DyeColor
+import net.minecraft.network.chat.TextColor
+import net.minecraft.world.item.DyeColor
fun DyeColor.toShedaniel(): me.shedaniel.math.Color =
- me.shedaniel.math.Color.ofOpaque(this.signColor)
+ me.shedaniel.math.Color.ofOpaque(this.textColor)
fun DyeColor.toTextColor(): TextColor =
- TextColor.fromRgb(this.signColor)
+ TextColor.fromRgb(this.textColor)
diff --git a/src/main/kotlin/util/customgui/CoordRememberingSlot.kt b/src/main/kotlin/util/customgui/CoordRememberingSlot.kt
index c61c711..e565850 100644
--- a/src/main/kotlin/util/customgui/CoordRememberingSlot.kt
+++ b/src/main/kotlin/util/customgui/CoordRememberingSlot.kt
@@ -1,7 +1,7 @@
package moe.nea.firmament.util.customgui
-import net.minecraft.screen.slot.Slot
+import net.minecraft.world.inventory.Slot
interface CoordRememberingSlot {
fun rememberCoords_firmament()
diff --git a/src/main/kotlin/util/customgui/CustomGui.kt b/src/main/kotlin/util/customgui/CustomGui.kt
index 35c60ac..f64bf4d 100644
--- a/src/main/kotlin/util/customgui/CustomGui.kt
+++ b/src/main/kotlin/util/customgui/CustomGui.kt
@@ -1,8 +1,11 @@
package moe.nea.firmament.util.customgui
import me.shedaniel.math.Rectangle
-import net.minecraft.client.gui.DrawContext
-import net.minecraft.screen.slot.Slot
+import net.minecraft.client.input.MouseButtonEvent
+import net.minecraft.client.gui.GuiGraphics
+import net.minecraft.client.input.CharacterEvent
+import net.minecraft.client.input.KeyEvent
+import net.minecraft.world.inventory.Slot
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.HandledScreenPushREIEvent
@@ -23,19 +26,19 @@ abstract class CustomGui {
}
open fun render(
- drawContext: DrawContext,
- delta: Float,
- mouseX: Int,
- mouseY: Int
+ drawContext: GuiGraphics,
+ delta: Float,
+ mouseX: Int,
+ mouseY: Int
) {
}
- open fun mouseClick(mouseX: Double, mouseY: Double, button: Int): Boolean {
+ open fun mouseClick(click: MouseButtonEvent, doubled: Boolean): Boolean {
return false
}
- open fun afterSlotRender(context: DrawContext, slot: Slot) {}
- open fun beforeSlotRender(context: DrawContext, slot: Slot) {}
+ open fun afterSlotRender(context: GuiGraphics, slot: Slot) {}
+ open fun beforeSlotRender(context: GuiGraphics, slot: Slot) {}
open fun mouseScrolled(mouseX: Double, mouseY: Double, horizontalAmount: Double, verticalAmount: Double): Boolean {
return false
}
@@ -69,23 +72,23 @@ abstract class CustomGui {
return true
}
- open fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean {
+ open fun mouseReleased(click: MouseButtonEvent): Boolean {
return false
}
- open fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean {
+ open fun mouseDragged(click: MouseButtonEvent, offsetX: Double, offsetY: Double): Boolean {
return false
}
- open fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
+ open fun keyPressed(input: KeyEvent): Boolean {
return false
}
- open fun charTyped(chr: Char, modifiers: Int): Boolean {
+ open fun charTyped(input: CharacterEvent): Boolean {
return false
}
- open fun keyReleased(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
+ open fun keyReleased(input: KeyEvent): Boolean {
return false
}
}
diff --git a/src/main/kotlin/util/customgui/HasCustomGui.kt b/src/main/kotlin/util/customgui/HasCustomGui.kt
index edead2e..7182979 100644
--- a/src/main/kotlin/util/customgui/HasCustomGui.kt
+++ b/src/main/kotlin/util/customgui/HasCustomGui.kt
@@ -1,7 +1,7 @@
package moe.nea.firmament.util.customgui
-import net.minecraft.client.gui.screen.ingame.HandledScreen
+import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen
@Suppress("FunctionName")
interface HasCustomGui {
@@ -9,7 +9,7 @@ interface HasCustomGui {
fun setCustomGui_Firmament(gui: CustomGui?)
}
-var <T : HandledScreen<*>> T.customGui: CustomGui?
+var <T : AbstractContainerScreen<*>> T.customGui: CustomGui?
get() = (this as HasCustomGui).getCustomGui_Firmament()
set(value) {
(this as HasCustomGui).setCustomGui_Firmament(value)
diff --git a/src/main/kotlin/util/data/Config.kt b/src/main/kotlin/util/data/Config.kt
new file mode 100644
index 0000000..41de039
--- /dev/null
+++ b/src/main/kotlin/util/data/Config.kt
@@ -0,0 +1,15 @@
+package moe.nea.firmament.util.data
+
+import moe.nea.firmament.util.compatloader.CompatLoader
+
+@Retention(AnnotationRetention.RUNTIME)
+@Target(AnnotationTarget.CLASS)
+annotation class Config(val prefix: String = "")
+
+
+interface IConfigProvider {
+ val configs: List<IDataHolder<*>>
+ companion object {
+ val providers = CompatLoader(IConfigProvider::class)
+ }
+}
diff --git a/src/main/kotlin/util/data/DataHolder.kt b/src/main/kotlin/util/data/DataHolder.kt
index 21a6014..c138d78 100644
--- a/src/main/kotlin/util/data/DataHolder.kt
+++ b/src/main/kotlin/util/data/DataHolder.kt
@@ -1,62 +1,13 @@
-
-
package moe.nea.firmament.util.data
-import java.nio.file.Path
import kotlinx.serialization.KSerializer
-import kotlin.io.path.exists
-import kotlin.io.path.readText
-import kotlin.io.path.writeText
-import moe.nea.firmament.Firmament
+import moe.nea.firmament.gui.config.storage.ConfigStorageClass
abstract class DataHolder<T>(
- val serializer: KSerializer<T>,
- val name: String,
- val default: () -> T
-) : IDataHolder<T> {
-
-
- final override var data: T
- private set
-
- init {
- data = readValueOrDefault()
- IDataHolder.putDataHolder(this::class, this)
- }
-
- private val file: Path get() = Firmament.CONFIG_DIR.resolve("$name.json")
-
- protected fun readValueOrDefault(): T {
- if (file.exists())
- try {
- return Firmament.json.decodeFromString(
- serializer,
- file.readText()
- )
- } catch (e: Exception) {/* Expecting IOException and SerializationException, but Kotlin doesn't allow multi catches*/
- IDataHolder.badLoads.add(name)
- Firmament.logger.error(
- "Exception during loading of config file $name. This will reset this config.",
- e
- )
- }
- return default()
- }
-
- private fun writeValue(t: T) {
- file.writeText(Firmament.json.encodeToString(serializer, t))
- }
-
- override fun save() {
- writeValue(data)
- }
-
- override fun load() {
- data = readValueOrDefault()
- }
-
- override fun markDirty() {
- IDataHolder.markDirty(this::class)
- }
-
+ serializer: KSerializer<T>,
+ name: String,
+ default: () -> T
+) : GenericConfig<T>(name, serializer, default) {
+ override val storageClass: ConfigStorageClass
+ get() = ConfigStorageClass.STORAGE
}
diff --git a/src/main/kotlin/util/data/IDataHolder.kt b/src/main/kotlin/util/data/IDataHolder.kt
index 1e9ba98..3229011 100644
--- a/src/main/kotlin/util/data/IDataHolder.kt
+++ b/src/main/kotlin/util/data/IDataHolder.kt
@@ -1,71 +1,117 @@
package moe.nea.firmament.util.data
-import java.util.concurrent.CopyOnWriteArrayList
-import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents
-import kotlin.reflect.KClass
-import net.minecraft.text.Text
+import java.util.UUID
+import java.util.concurrent.CompletableFuture
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.json.JsonObject
+import kotlinx.serialization.json.buildJsonObject
import moe.nea.firmament.Firmament
-import moe.nea.firmament.events.ScreenChangeEvent
-import moe.nea.firmament.util.MC
+import moe.nea.firmament.gui.config.storage.ConfigStorageClass
+import moe.nea.firmament.gui.config.storage.FirmamentConfigLoader
+import moe.nea.firmament.util.SBData
-interface IDataHolder<T> {
- companion object {
- internal var badLoads: MutableList<String> = CopyOnWriteArrayList()
- private val allConfigs: MutableMap<KClass<out IDataHolder<*>>, IDataHolder<*>> = mutableMapOf()
- private val dirty: MutableSet<KClass<out IDataHolder<*>>> = mutableSetOf()
+sealed class IDataHolder<T> {
+ fun markDirty(future: CompletableFuture<Void?>? = null) {
+ FirmamentConfigLoader.markDirty(this, future)
+ }
- internal fun <T : IDataHolder<K>, K> putDataHolder(kClass: KClass<T>, inst: IDataHolder<K>) {
- allConfigs[kClass] = inst
- }
+ init {
+ require(this.javaClass.getAnnotation(Config::class.java) != null)
+ }
+
+ abstract fun keys(): Collection<T>
+ abstract fun saveTo(key: T): JsonObject
+ abstract fun loadFrom(key: T, jsonObject: JsonObject)
+ abstract fun explicitDefaultLoad()
+ abstract fun clear()
+ abstract val storageClass: ConfigStorageClass
+}
+
+open class ProfileKeyedConfig<T>(
+ val prefix: String,
+ val serializer: KSerializer<T>,
+ val default: () -> T & Any,
+) : IDataHolder<UUID>() {
+
+ override val storageClass: ConfigStorageClass
+ get() = ConfigStorageClass.PROFILE
+ private var _data: MutableMap<UUID, T>? = null
- fun <T : IDataHolder<K>, K> markDirty(kClass: KClass<T>) {
- if (kClass !in allConfigs) {
- Firmament.logger.error("Tried to markDirty '${kClass.qualifiedName}', which isn't registered as 'IConfigHolder'")
- return
- }
- dirty.add(kClass)
+ val data: T & Any
+ get() {
+ val map = _data ?: error("Config $this not loaded — forgot to register?")
+ map[SBData.profileIdOrNil]?.let { return it }
+ val newValue = default()
+ map[SBData.profileIdOrNil] = newValue
+ return newValue
}
- private fun performSaves() {
- val toSave = dirty.toList().also {
- dirty.clear()
- }
- for (it in toSave) {
- val obj = allConfigs[it]
- if (obj == null) {
- Firmament.logger.error("Tried to save '${it}', which isn't registered as 'ConfigHolder'")
- continue
- }
- obj.save()
- }
+ override fun keys(): Collection<UUID> {
+ return _data!!.keys
+ }
+
+ override fun saveTo(key: UUID): JsonObject {
+ val d = _data!!
+ return buildJsonObject {
+ put(prefix, Firmament.json.encodeToJsonElement(serializer, d[key] ?: return@buildJsonObject))
}
+ }
- private fun warnForResetConfigs() {
- if (badLoads.isNotEmpty()) {
- MC.sendChat(
- Text.literal(
- "The following configs have been reset: ${badLoads.joinToString(", ")}. " +
- "This can be intentional, but probably isn't."
- )
- )
- badLoads.clear()
- }
+ override fun loadFrom(key: UUID, jsonObject: JsonObject) {
+ var map = _data
+ if (map == null) {
+ map = mutableMapOf()
+ _data = map
}
+ map[key] =
+ jsonObject[prefix]
+ ?.let {
+ Firmament.json.decodeFromJsonElement(serializer, it)
+ } ?: default()
+ }
- fun registerEvents() {
- ScreenChangeEvent.subscribe("IDataHolder:saveOnScreenChange") { event ->
- performSaves()
- warnForResetConfigs()
- }
- ClientLifecycleEvents.CLIENT_STOPPING.register(ClientLifecycleEvents.ClientStopping {
- performSaves()
- })
+ override fun explicitDefaultLoad() {
+ _data = mutableMapOf()
+ }
+
+ override fun clear() {
+ _data = null
+ }
+}
+
+abstract class GenericConfig<T>(
+ val prefix: String,
+ val serializer: KSerializer<T>,
+ val default: () -> T,
+) : IDataHolder<Unit>() {
+
+ private var _data: T? = null
+
+ val data get() = _data ?: error("Config $this not loaded — forgot to register?")
+
+ override fun keys(): Collection<Unit> {
+ return listOf(Unit)
+ }
+
+ override fun explicitDefaultLoad() {
+ _data = default()
+ }
+
+ open fun onLoad() {
+ }
+
+ override fun saveTo(key: Unit): JsonObject {
+ return buildJsonObject {
+ put(prefix, Firmament.json.encodeToJsonElement(serializer, data))
}
+ }
+ override fun loadFrom(key: Unit, jsonObject: JsonObject) {
+ _data = jsonObject[prefix]?.let { Firmament.json.decodeFromJsonElement(serializer, it) } ?: default()
+ onLoad()
}
- val data: T
- fun save()
- fun markDirty()
- fun load()
+ override fun clear() {
+ _data = null
+ }
}
diff --git a/src/main/kotlin/util/data/MultiFileDataHolder.kt b/src/main/kotlin/util/data/MultiFileDataHolder.kt
deleted file mode 100644
index 94c6f05..0000000
--- a/src/main/kotlin/util/data/MultiFileDataHolder.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-package moe.nea.firmament.util.data
-
-import kotlinx.serialization.KSerializer
-import kotlin.io.path.createDirectories
-import kotlin.io.path.deleteExisting
-import kotlin.io.path.exists
-import kotlin.io.path.extension
-import kotlin.io.path.listDirectoryEntries
-import kotlin.io.path.nameWithoutExtension
-import kotlin.io.path.readText
-import kotlin.io.path.writeText
-import moe.nea.firmament.Firmament
-
-abstract class MultiFileDataHolder<T>(
- val dataSerializer: KSerializer<T>,
- val configName: String
-) { // TODO: abstract this + ProfileSpecificDataHolder
- val configDirectory = Firmament.CONFIG_DIR.resolve(configName)
- private var allData = readValues()
- protected fun readValues(): MutableMap<String, T> {
- if (!configDirectory.exists()) {
- configDirectory.createDirectories()
- }
- val profileFiles = configDirectory.listDirectoryEntries()
- return profileFiles
- .filter { it.extension == "json" }
- .mapNotNull {
- try {
- it.nameWithoutExtension to Firmament.json.decodeFromString(dataSerializer, it.readText())
- } catch (e: Exception) { /* Expecting IOException and SerializationException, but Kotlin doesn't allow multi catches*/
- IDataHolder.badLoads.add(configName)
- Firmament.logger.error(
- "Exception during loading of multi file data holder $it ($configName). This will reset that profiles config.",
- e
- )
- null
- }
- }.toMap().toMutableMap()
- }
-
- fun save() {
- if (!configDirectory.exists()) {
- configDirectory.createDirectories()
- }
- val c = allData
- configDirectory.listDirectoryEntries().forEach {
- if (it.nameWithoutExtension !in c.mapKeys { it.toString() }) {
- it.deleteExisting()
- }
- }
- c.forEach { (name, value) ->
- val f = configDirectory.resolve("$name.json")
- f.writeText(Firmament.json.encodeToString(dataSerializer, value))
- }
- }
-
- fun list(): Map<String, T> = allData
- val validPathRegex = "[a-zA-Z0-9_][a-zA-Z0-9\\-_.]*".toPattern()
- fun insert(name: String, value: T) {
- require(validPathRegex.matcher(name).matches()) { "Not a valid name: $name" }
- allData[name] = value
- }
-}
diff --git a/src/main/kotlin/util/data/ProfileSpecificDataHolder.kt b/src/main/kotlin/util/data/ProfileSpecificDataHolder.kt
index 1cd4f22..853ba7d 100644
--- a/src/main/kotlin/util/data/ProfileSpecificDataHolder.kt
+++ b/src/main/kotlin/util/data/ProfileSpecificDataHolder.kt
@@ -1,84 +1,9 @@
-
-
package moe.nea.firmament.util.data
-import java.nio.file.Path
-import java.util.UUID
import kotlinx.serialization.KSerializer
-import kotlin.io.path.createDirectories
-import kotlin.io.path.deleteExisting
-import kotlin.io.path.exists
-import kotlin.io.path.extension
-import kotlin.io.path.listDirectoryEntries
-import kotlin.io.path.nameWithoutExtension
-import kotlin.io.path.readText
-import kotlin.io.path.writeText
-import moe.nea.firmament.Firmament
-import moe.nea.firmament.util.SBData
abstract class ProfileSpecificDataHolder<S>(
- private val dataSerializer: KSerializer<S>,
- val configName: String,
- private val configDefault: () -> S
-) : IDataHolder<S?> {
-
- var allConfigs: MutableMap<UUID, S>
-
- override val data: S?
- get() = SBData.profileId?.let {
- allConfigs.computeIfAbsent(it) { configDefault() }
- }
-
- init {
- allConfigs = readValues()
- IDataHolder.putDataHolder(this::class, this)
- }
-
- private val configDirectory: Path get() = Firmament.CONFIG_DIR.resolve("profiles").resolve(configName)
-
- private fun readValues(): MutableMap<UUID, S> {
- if (!configDirectory.exists()) {
- configDirectory.createDirectories()
- }
- val profileFiles = configDirectory.listDirectoryEntries()
- return profileFiles
- .filter { it.extension == "json" }
- .mapNotNull {
- try {
- UUID.fromString(it.nameWithoutExtension) to Firmament.json.decodeFromString(dataSerializer, it.readText())
- } catch (e: Exception) { /* Expecting IOException and SerializationException, but Kotlin doesn't allow multi catches*/
- IDataHolder.badLoads.add(configName)
- Firmament.logger.error(
- "Exception during loading of profile specific config file $it ($configName). This will reset that profiles config.",
- e
- )
- null
- }
- }.toMap().toMutableMap()
- }
-
- override fun save() {
- if (!configDirectory.exists()) {
- configDirectory.createDirectories()
- }
- val c = allConfigs
- configDirectory.listDirectoryEntries().forEach {
- if (it.nameWithoutExtension !in c.mapKeys { it.toString() }) {
- it.deleteExisting()
- }
- }
- c.forEach { (name, value) ->
- val f = configDirectory.resolve("$name.json")
- f.writeText(Firmament.json.encodeToString(dataSerializer, value))
- }
- }
-
- override fun markDirty() {
- IDataHolder.markDirty(this::class)
- }
-
- override fun load() {
- allConfigs = readValues()
- }
-
-}
+ dataSerializer: KSerializer<S>,
+ configName: String,
+ configDefault: () -> S & Any
+) : ProfileKeyedConfig<S>(configName, dataSerializer, configDefault)
diff --git a/src/main/kotlin/util/json/BlockPosSerializer.kt b/src/main/kotlin/util/json/BlockPosSerializer.kt
index 144b0a0..5906544 100644
--- a/src/main/kotlin/util/json/BlockPosSerializer.kt
+++ b/src/main/kotlin/util/json/BlockPosSerializer.kt
@@ -5,7 +5,7 @@ import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.serializer
-import net.minecraft.util.math.BlockPos
+import net.minecraft.core.BlockPos
object BlockPosSerializer : KSerializer<BlockPos> {
val delegate = serializer<List<Int>>()
diff --git a/src/main/kotlin/util/json/CodecSerializer.kt b/src/main/kotlin/util/json/CodecSerializer.kt
new file mode 100644
index 0000000..9ea08ad
--- /dev/null
+++ b/src/main/kotlin/util/json/CodecSerializer.kt
@@ -0,0 +1,26 @@
+package util.json
+
+import com.mojang.serialization.Codec
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.descriptors.SerialDescriptor
+import kotlinx.serialization.encoding.Decoder
+import kotlinx.serialization.encoding.Encoder
+import kotlinx.serialization.json.JsonElement
+import moe.nea.firmament.util.json.KJsonOps
+
+abstract class CodecSerializer<T>(val codec: Codec<T>) : KSerializer<T> {
+ override val descriptor: SerialDescriptor
+ get() = JsonElement.serializer().descriptor
+
+ override fun serialize(encoder: Encoder, value: T) {
+ encoder.encodeSerializableValue(
+ JsonElement.serializer(),
+ codec.encodeStart(KJsonOps.INSTANCE, value).orThrow
+ )
+ }
+
+ override fun deserialize(decoder: Decoder): T {
+ return codec.decode(KJsonOps.INSTANCE, decoder.decodeSerializableValue(JsonElement.serializer()))
+ .orThrow.first
+ }
+}
diff --git a/src/main/kotlin/util/json/DashlessUUIDSerializer.kt b/src/main/kotlin/util/json/DashlessUUIDSerializer.kt
index acb1dc8..f4b073a 100644
--- a/src/main/kotlin/util/json/DashlessUUIDSerializer.kt
+++ b/src/main/kotlin/util/json/DashlessUUIDSerializer.kt
@@ -9,7 +9,7 @@ import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
-import moe.nea.firmament.util.parseDashlessUUID
+import moe.nea.firmament.util.parsePotentiallyDashlessUUID
object DashlessUUIDSerializer : KSerializer<UUID> {
override val descriptor: SerialDescriptor =
@@ -17,10 +17,7 @@ object DashlessUUIDSerializer : KSerializer<UUID> {
override fun deserialize(decoder: Decoder): UUID {
val str = decoder.decodeString()
- if ("-" in str) {
- return UUID.fromString(str)
- }
- return parseDashlessUUID(str)
+ return parsePotentiallyDashlessUUID(str)
}
override fun serialize(encoder: Encoder, value: UUID) {
diff --git a/src/main/kotlin/util/json/FirmCodecs.kt b/src/main/kotlin/util/json/FirmCodecs.kt
index c0863bc..d7b8f57 100644
--- a/src/main/kotlin/util/json/FirmCodecs.kt
+++ b/src/main/kotlin/util/json/FirmCodecs.kt
@@ -4,11 +4,11 @@ import com.mojang.serialization.Codec
import com.mojang.serialization.DataResult
import com.mojang.serialization.Lifecycle
import com.mojang.util.UndashedUuid
-import net.minecraft.util.Uuids
+import net.minecraft.core.UUIDUtil
object FirmCodecs {
@JvmField
- val UUID_LENIENT_PREFER_INT_STREAM = Codec.withAlternative(Uuids.INT_STREAM_CODEC, Codec.STRING.comapFlatMap(
+ val UUID_LENIENT_PREFER_INT_STREAM = Codec.withAlternative(UUIDUtil.CODEC, Codec.STRING.comapFlatMap(
{
try {
DataResult.success(UndashedUuid.fromStringLenient(it), Lifecycle.stable())
diff --git a/src/main/kotlin/util/json/InstantAsLongSerializer.kt b/src/main/kotlin/util/json/InstantAsLongSerializer.kt
index ad738dc..51b5f0a 100644
--- a/src/main/kotlin/util/json/InstantAsLongSerializer.kt
+++ b/src/main/kotlin/util/json/InstantAsLongSerializer.kt
@@ -2,7 +2,7 @@
package moe.nea.firmament.util.json
-import kotlinx.datetime.Instant
+import java.time.Instant
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
@@ -13,10 +13,10 @@ import kotlinx.serialization.encoding.Encoder
object InstantAsLongSerializer : KSerializer<Instant> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("InstantAsLongSerializer", PrimitiveKind.LONG)
override fun deserialize(decoder: Decoder): Instant {
- return Instant.fromEpochMilliseconds(decoder.decodeLong())
+ return Instant.ofEpochMilli(decoder.decodeLong())
}
override fun serialize(encoder: Encoder, value: Instant) {
- encoder.encodeLong(value.toEpochMilliseconds())
+ encoder.encodeLong(value.toEpochMilli())
}
}
diff --git a/src/main/kotlin/util/json/KJsonUtils.kt b/src/main/kotlin/util/json/KJsonUtils.kt
new file mode 100644
index 0000000..b15119b
--- /dev/null
+++ b/src/main/kotlin/util/json/KJsonUtils.kt
@@ -0,0 +1,11 @@
+package moe.nea.firmament.util.json
+
+import kotlinx.serialization.json.JsonArray
+import kotlinx.serialization.json.JsonElement
+import kotlinx.serialization.json.JsonPrimitive
+
+fun <T : JsonElement> List<T>.asJsonArray(): JsonArray {
+ return JsonArray(this)
+}
+
+fun Iterable<String>.toJsonArray(): JsonArray = map { JsonPrimitive(it) }.asJsonArray()
diff --git a/src/main/kotlin/util/json/jsonConversion.kt b/src/main/kotlin/util/json/jsonConversion.kt
new file mode 100644
index 0000000..f921f7b
--- /dev/null
+++ b/src/main/kotlin/util/json/jsonConversion.kt
@@ -0,0 +1,65 @@
+package moe.nea.firmament.util.json
+
+import com.google.gson.JsonArray
+import com.google.gson.JsonElement
+import com.google.gson.JsonNull
+import com.google.gson.JsonObject
+import com.google.gson.JsonPrimitive
+import com.google.gson.internal.LazilyParsedNumber
+
+
+fun JsonElement.intoKotlinJson(): kotlinx.serialization.json.JsonElement {
+ when (this) {
+ is JsonNull -> return kotlinx.serialization.json.JsonNull
+ is JsonObject -> {
+ return kotlinx.serialization.json.JsonObject(
+ this.entrySet()
+ .associate { it.key to it.value.intoKotlinJson() })
+ }
+
+ is JsonArray -> {
+ return kotlinx.serialization.json.JsonArray(this.map { it.intoKotlinJson() })
+ }
+
+ is JsonPrimitive -> {
+ if (this.isString)
+ return kotlinx.serialization.json.JsonPrimitive(this.asString)
+ if (this.isBoolean)
+ return kotlinx.serialization.json.JsonPrimitive(this.asBoolean)
+ return kotlinx.serialization.json.JsonPrimitive(this.asNumber)
+ }
+
+ else -> error("Unknown json variant $this")
+ }
+}
+
+fun kotlinx.serialization.json.JsonElement.intoGson(): JsonElement {
+ when (this) {
+ is kotlinx.serialization.json.JsonNull -> return JsonNull.INSTANCE
+ is kotlinx.serialization.json.JsonPrimitive -> {
+ if (this.isString)
+ return JsonPrimitive(this.content)
+ if (this.content == "true")
+ return JsonPrimitive(true)
+ if (this.content == "false")
+ return JsonPrimitive(false)
+ return JsonPrimitive(LazilyParsedNumber(this.content))
+ }
+
+ is kotlinx.serialization.json.JsonObject -> {
+ val obj = JsonObject()
+ for ((k, v) in this) {
+ obj.add(k, v.intoGson())
+ }
+ return obj
+ }
+
+ is kotlinx.serialization.json.JsonArray -> {
+ val arr = JsonArray()
+ for (v in this) {
+ arr.add(v.intoGson())
+ }
+ return arr
+ }
+ }
+}
diff --git a/src/main/kotlin/util/math/GChainReconciliation.kt b/src/main/kotlin/util/math/GChainReconciliation.kt
new file mode 100644
index 0000000..37998d5
--- /dev/null
+++ b/src/main/kotlin/util/math/GChainReconciliation.kt
@@ -0,0 +1,102 @@
+package moe.nea.firmament.util.math
+
+import kotlin.math.min
+
+/**
+ * Algorithm for (sort of) cheap reconciliation of two cycles with missing frames.
+ */
+object GChainReconciliation {
+ // Step one: Find the most common element and shift the arrays until it is at the start in both (this could be just rotating until minimal levenshtein distance or smth. that would be way better for cycles with duplicates, but i do not want to implement levenshtein as well)
+ // Step two: Find the first different element.
+ // Step three: Find the next index of both of the elements.
+ // Step four: Insert the element that is further away.
+
+ fun <T> Iterable<T>.frequencies(): Map<T, Int> {
+ val acc = mutableMapOf<T, Int>()
+ for (t in this) {
+ acc.compute(t, { _, old -> (old ?: 0) + 1 })
+ }
+ return acc
+ }
+
+ fun <T> findMostCommonlySharedElement(
+ leftChain: List<T>,
+ rightChain: List<T>,
+ ): T {
+ val lf = leftChain.frequencies()
+ val rf = rightChain.frequencies()
+ val mostCommonlySharedElement = lf.maxByOrNull { min(it.value, rf[it.key] ?: 0) }?.key
+ if (mostCommonlySharedElement == null || mostCommonlySharedElement !in rf)
+ error("Could not find a shared element")
+ return mostCommonlySharedElement
+ }
+
+ fun <T> List<T>.getMod(index: Int): T {
+ return this[index.mod(size)]
+ }
+
+ fun <T> List<T>.rotated(offset: Int): List<T> {
+ val newList = mutableListOf<T>()
+ for (index in indices) {
+ newList.add(getMod(index - offset))
+ }
+ return newList
+ }
+
+ fun <T> shiftToFront(list: List<T>, element: T): List<T> {
+ val shiftDistance = list.indexOf(element)
+ require(shiftDistance >= 0)
+ return list.rotated(-shiftDistance)
+ }
+
+ fun <T> List<T>.indexOfOrMaxInt(element: T): Int = indexOf(element).takeUnless { it < 0 } ?: Int.MAX_VALUE
+
+ fun <T> reconcileCycles(
+ leftChain: List<T>,
+ rightChain: List<T>,
+ ): List<T> {
+ val mostCommonElement = findMostCommonlySharedElement(leftChain, rightChain)
+ val left = shiftToFront(leftChain, mostCommonElement).toMutableList()
+ val right = shiftToFront(rightChain, mostCommonElement).toMutableList()
+
+ var index = 0
+ while (index < left.size && index < right.size) {
+ val leftEl = left[index]
+ val rightEl = right[index]
+ if (leftEl == rightEl) {
+ index++
+ continue
+ }
+ val nextLeftInRight = right.subList(index, right.size)
+ .indexOfOrMaxInt(leftEl)
+
+ val nextRightInLeft = left.subList(index, left.size)
+ .indexOfOrMaxInt(rightEl)
+ if (nextLeftInRight < nextRightInLeft) {
+ left.add(index, rightEl)
+ } else if (nextRightInLeft < nextLeftInRight) {
+ right.add(index, leftEl)
+ } else {
+ index++
+ }
+ }
+ return if (left.size < right.size) right else left
+ }
+
+ fun <T> isValidCycle(longList: List<T>, cycle: List<T>): Boolean {
+ for ((i, value) in longList.withIndex()) {
+ if (cycle.getMod(i) != value)
+ return false
+ }
+ return true
+ }
+
+ fun <T> List<T>.shortenCycle(): List<T> {
+ for (i in (1..<size)) {
+ if (isValidCycle(this, subList(0, i)))
+ return subList(0, i)
+ }
+ return this
+ }
+
+}
diff --git a/src/main/kotlin/util/math/Projections.kt b/src/main/kotlin/util/math/Projections.kt
new file mode 100644
index 0000000..9e9f844
--- /dev/null
+++ b/src/main/kotlin/util/math/Projections.kt
@@ -0,0 +1,46 @@
+package moe.nea.firmament.util.math
+
+import kotlin.math.absoluteValue
+import kotlin.math.cos
+import kotlin.math.sin
+import net.minecraft.world.phys.Vec2
+import moe.nea.firmament.util.render.wrapAngle
+
+object Projections {
+ object Two {
+ val ε = 1e-6
+ val π = moe.nea.firmament.util.render.π
+ val τ = 2 * π
+
+ fun isNullish(float: Float) = float.absoluteValue < ε
+
+ fun xInterceptOfLine(origin: Vec2, direction: Vec2): Vec2? {
+ if (isNullish(direction.x))
+ return Vec2(origin.x, 0F)
+ if (isNullish(direction.y))
+ return null
+
+ val slope = direction.y / direction.x
+ return Vec2(origin.x - origin.y / slope, 0F)
+ }
+
+ fun interceptAlongCardinal(distanceFromAxis: Float, slope: Float): Float? {
+ if (isNullish(slope))
+ return null
+ return -distanceFromAxis / slope
+ }
+
+ fun projectAngleOntoUnitBox(angleRadians: Double): Vec2 {
+ val angleRadians = wrapAngle(angleRadians)
+ val cx = cos(angleRadians)
+ val cy = sin(angleRadians)
+
+ val ex = 1 / cx.absoluteValue
+ val ey = 1 / cy.absoluteValue
+
+ val e = minOf(ex, ey)
+
+ return Vec2((cx * e).toFloat(), (cy * e).toFloat())
+ }
+ }
+}
diff --git a/src/main/kotlin/util/mc/ArmorUtil.kt b/src/main/kotlin/util/mc/ArmorUtil.kt
new file mode 100644
index 0000000..3bb1768
--- /dev/null
+++ b/src/main/kotlin/util/mc/ArmorUtil.kt
@@ -0,0 +1,8 @@
+package moe.nea.firmament.util.mc
+
+import net.minecraft.world.entity.EquipmentSlot
+import net.minecraft.world.entity.LivingEntity
+
+val LivingEntity.iterableArmorItems
+ get() = EquipmentSlot.entries.asSequence()
+ .map { it to getItemBySlot(it) }
diff --git a/src/main/kotlin/util/mc/CustomRenderPassHelper.kt b/src/main/kotlin/util/mc/CustomRenderPassHelper.kt
new file mode 100644
index 0000000..93cd7c1
--- /dev/null
+++ b/src/main/kotlin/util/mc/CustomRenderPassHelper.kt
@@ -0,0 +1,161 @@
+package moe.nea.firmament.util.mc
+
+import com.mojang.blaze3d.buffers.GpuBuffer
+import com.mojang.blaze3d.buffers.GpuBufferSlice
+import com.mojang.blaze3d.buffers.Std140Builder
+import com.mojang.blaze3d.pipeline.RenderPipeline
+import com.mojang.blaze3d.systems.RenderPass
+import com.mojang.blaze3d.systems.RenderSystem
+import com.mojang.blaze3d.vertex.VertexFormat
+import java.nio.ByteBuffer
+import java.nio.ByteOrder
+import java.util.OptionalDouble
+import java.util.OptionalInt
+import org.joml.Vector3f
+import org.joml.Vector4f
+import com.mojang.blaze3d.pipeline.RenderTarget
+import com.mojang.blaze3d.vertex.BufferBuilder
+import com.mojang.blaze3d.vertex.MeshData
+import net.minecraft.client.renderer.texture.AbstractTexture
+import com.mojang.blaze3d.vertex.ByteBufferBuilder
+import net.minecraft.resources.ResourceLocation
+import net.minecraft.util.Mth
+import moe.nea.firmament.util.ErrorUtil
+import moe.nea.firmament.util.MC
+
+
+class CustomRenderPassHelper(
+ val labelSupplier: () -> String,
+ val drawMode: VertexFormat.Mode,
+ val vertexFormat: VertexFormat,
+ val frameBuffer: RenderTarget,
+ val hasDepth: Boolean,
+) : AutoCloseable {
+ private val scope = mutableListOf<AutoCloseable>()
+ private val preparations = mutableListOf<(RenderPass) -> Unit>()
+ val device = RenderSystem.getDevice()
+ private var hasPipelineAction = false
+ private var hasSetDefaultUniforms = false
+ val commandEncoder = device.createCommandEncoder()
+ fun setPipeline(pipeline: RenderPipeline) {
+ ErrorUtil.softCheck("Already has a pipeline", !hasPipelineAction)
+ hasPipelineAction = true
+ queueAction {
+ it.setPipeline(pipeline)
+ }
+ }
+
+ fun bindSampler(name: String, texture: ResourceLocation) {
+ bindSampler(name, MC.textureManager.getTexture(texture))
+ }
+
+ fun bindSampler(name: String, texture: AbstractTexture) {
+ queueAction { it.bindSampler(name, texture.textureView) }
+ }
+
+
+ fun dontSetDefaultUniforms() {
+ hasSetDefaultUniforms = true
+ }
+
+ fun setAllDefaultUniforms() {
+ hasSetDefaultUniforms = true
+ queueAction {
+ RenderSystem.bindDefaultUniforms(it)
+ }
+ setUniform(
+ "DynamicTransforms", RenderSystem.getDynamicUniforms()
+ .writeTransform(
+ RenderSystem.getModelViewMatrix(),
+ Vector4f(1.0F, 1.0F, 1.0F, 1.0F),
+ Vector3f(), // TODO: 1.21.10
+ RenderSystem.getTextureMatrix(),
+ RenderSystem.getShaderLineWidth()
+ )
+ )
+ }
+
+ fun setUniform(name: String, slice: GpuBufferSlice) = queueAction { it.setUniform(name, slice) }
+ fun setUniform(name: String, slice: GpuBuffer) = queueAction { it.setUniform(name, slice) }
+
+ fun setUniform(name: String, size: Int, labelSupplier: () -> String = { name }, init: (Std140Builder) -> Unit) {
+ val buffer = createUniformBuffer(labelSupplier, allocateByteBuf(size, init))
+ setUniform(name, buffer)
+ }
+
+ var vertices: MeshData? = null
+
+ fun uploadVertices(size: Int, init: (BufferBuilder) -> Unit) {
+ uploadVertices(
+ BufferBuilder(queueClose(ByteBufferBuilder(size)), drawMode, vertexFormat)
+ .also(init)
+ .buildOrThrow()
+ )
+ }
+
+ fun uploadVertices(buffer: MeshData) {
+ queueClose(buffer)
+ ErrorUtil.softCheck("Vertices have already been uploaded", vertices == null)
+ vertices = buffer
+ val vertexBuffer = vertexFormat.uploadImmediateVertexBuffer(buffer.vertexBuffer())
+ val indexBufferConstructor = RenderSystem.getSequentialBuffer(drawMode)
+ val indexBuffer = indexBufferConstructor.getBuffer(buffer.drawState().indexCount)
+ queueAction {
+ it.setIndexBuffer(indexBuffer, indexBufferConstructor.type())
+ it.setVertexBuffer(0, vertexBuffer)
+ }
+ }
+
+ fun createUniformBuffer(labelSupplier: () -> String, buffer: ByteBuffer): GpuBuffer {
+ return queueClose(
+ device.createBuffer(
+ labelSupplier::invoke,
+ GpuBuffer.USAGE_UNIFORM or GpuBuffer.USAGE_MAP_READ,
+ buffer
+ )
+ )
+ }
+
+ fun allocateByteBuf(size: Int, init: (Std140Builder) -> Unit): ByteBuffer {
+ return Std140Builder.intoBuffer( // TODO: i really dont know about this 16 align? but it seems to be generally correct.
+ ByteBuffer
+ .allocateDirect(Mth.roundToward(size, 16))
+ .order(ByteOrder.nativeOrder())
+ ).also(init).get()
+ }
+
+ fun queueAction(action: (RenderPass) -> Unit) {
+ preparations.add(action)
+ }
+
+ fun <T : AutoCloseable> queueClose(t: T): T = t.also { scope.add(it) }
+ override fun close() {
+ scope.reversed().forEach { it.close() }
+ }
+
+ object DrawToken
+
+ fun draw(): DrawToken {
+ val vertexData = (ErrorUtil.notNullOr(vertices, "No vertex data uploaded") { return DrawToken })
+ ErrorUtil.softCheck("Missing default uniforms", hasSetDefaultUniforms)
+ ErrorUtil.softCheck("Missing a pipeline", hasPipelineAction)
+ val renderPass = queueClose(
+ commandEncoder.createRenderPass(
+ labelSupplier::invoke,
+ RenderSystem.outputColorTextureOverride ?: frameBuffer.colorTextureView!!,
+ OptionalInt.empty(),
+ (RenderSystem.outputDepthTextureOverride
+ ?: frameBuffer.depthTextureView).takeIf { frameBuffer.useDepth && hasDepth },
+ OptionalDouble.empty()
+ )
+ )
+ preparations.forEach { it(renderPass) }
+ renderPass.drawIndexed(
+ 0,
+ 0,
+ vertexData.drawState().indexCount,
+ 1
+ )
+ return DrawToken
+ }
+}
diff --git a/src/main/kotlin/util/mc/FakeInventory.kt b/src/main/kotlin/util/mc/FakeInventory.kt
index 26c04bc..198ec68 100644
--- a/src/main/kotlin/util/mc/FakeInventory.kt
+++ b/src/main/kotlin/util/mc/FakeInventory.kt
@@ -1,14 +1,14 @@
package util.mc
-import net.minecraft.entity.player.PlayerEntity
-import net.minecraft.inventory.Inventory
-import net.minecraft.item.ItemStack
+import net.minecraft.world.entity.player.Player
+import net.minecraft.world.Container
+import net.minecraft.world.item.ItemStack
-class FakeInventory(val stack: ItemStack) : Inventory {
- override fun clear() {
+class FakeInventory(val stack: ItemStack) : Container {
+ override fun clearContent() {
}
- override fun size(): Int {
+ override fun getContainerSize(): Int {
return 1
}
@@ -16,26 +16,26 @@ class FakeInventory(val stack: ItemStack) : Inventory {
return stack.isEmpty
}
- override fun getStack(slot: Int): ItemStack {
+ override fun getItem(slot: Int): ItemStack {
require(slot == 0)
return stack
}
- override fun removeStack(slot: Int, amount: Int): ItemStack {
+ override fun removeItem(slot: Int, amount: Int): ItemStack {
return ItemStack.EMPTY
}
- override fun removeStack(slot: Int): ItemStack {
+ override fun removeItemNoUpdate(slot: Int): ItemStack {
return ItemStack.EMPTY
}
- override fun setStack(slot: Int, stack: ItemStack?) {
+ override fun setItem(slot: Int, stack: ItemStack?) {
}
- override fun markDirty() {
+ override fun setChanged() {
}
- override fun canPlayerUse(player: PlayerEntity?): Boolean {
+ override fun stillValid(player: Player?): Boolean {
return true
}
}
diff --git a/src/main/kotlin/util/mc/FakeSlot.kt b/src/main/kotlin/util/mc/FakeSlot.kt
index a9be484..9793fdf 100644
--- a/src/main/kotlin/util/mc/FakeSlot.kt
+++ b/src/main/kotlin/util/mc/FakeSlot.kt
@@ -1,15 +1,15 @@
package moe.nea.firmament.util.mc
import util.mc.FakeInventory
-import net.minecraft.item.ItemStack
-import net.minecraft.screen.slot.Slot
+import net.minecraft.world.item.ItemStack
+import net.minecraft.world.inventory.Slot
class FakeSlot(
- stack: ItemStack,
- x: Int,
- y: Int
+ stack: ItemStack,
+ x: Int,
+ y: Int
) : Slot(FakeInventory(stack), 0, x, y) {
init {
- id = 0
+ index = 0
}
}
diff --git a/src/main/kotlin/util/mc/FirmamentDataComponentTypes.kt b/src/main/kotlin/util/mc/FirmamentDataComponentTypes.kt
index 0866665..79536e5 100644
--- a/src/main/kotlin/util/mc/FirmamentDataComponentTypes.kt
+++ b/src/main/kotlin/util/mc/FirmamentDataComponentTypes.kt
@@ -2,10 +2,10 @@ package moe.nea.firmament.util.mc
import com.mojang.serialization.Codec
import io.netty.buffer.ByteBuf
-import net.minecraft.component.ComponentType
-import net.minecraft.network.codec.PacketCodec
-import net.minecraft.registry.Registries
-import net.minecraft.registry.Registry
+import net.minecraft.core.component.DataComponentType
+import net.minecraft.network.codec.StreamCodec
+import net.minecraft.core.registries.BuiltInRegistries
+import net.minecraft.core.Registry
import moe.nea.firmament.Firmament
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.ClientInitEvent
@@ -19,18 +19,18 @@ object FirmamentDataComponentTypes {
private fun <T> register(
id: String,
- builderOperator: (ComponentType.Builder<T>) -> Unit
- ): ComponentType<T> {
+ builderOperator: (DataComponentType.Builder<T>) -> Unit
+ ): DataComponentType<T> {
return Registry.register(
- Registries.DATA_COMPONENT_TYPE,
+ BuiltInRegistries.DATA_COMPONENT_TYPE,
Firmament.identifier(id),
- ComponentType.builder<T>().also(builderOperator)
+ DataComponentType.builder<T>().also(builderOperator)
.build()
)
}
- fun <T> errorCodec(message: String): PacketCodec<in ByteBuf, T> =
- object : PacketCodec<ByteBuf, T> {
+ fun <T> errorCodec(message: String): StreamCodec<in ByteBuf, T> =
+ object : StreamCodec<ByteBuf, T> {
override fun decode(buf: ByteBuf?): T? {
error(message)
}
@@ -40,16 +40,16 @@ object FirmamentDataComponentTypes {
}
}
- fun <T, B : ComponentType.Builder<T>> B.neverEncode(message: String = "This element should never be encoded or decoded"): B {
- packetCodec(errorCodec(message))
- codec(null)
+ fun <T, B : DataComponentType.Builder<T>> B.neverEncode(message: String = "This element should never be encoded or decoded"): B {
+ networkSynchronized(errorCodec(message))
+ persistent(null)
return this
}
val IS_BROKEN = register<Boolean>(
"is_broken"
) {
- it.codec(Codec.BOOL.fieldOf("is_broken").codec())
+ it.persistent(Codec.BOOL.fieldOf("is_broken").codec())
}
val CUSTOM_MINING_BLOCK_DATA = register<MiningRepoData.CustomMiningBlock>("custom_mining_block") {
diff --git a/src/main/kotlin/util/mc/InitLevel.kt b/src/main/kotlin/util/mc/InitLevel.kt
new file mode 100644
index 0000000..2c3eedb
--- /dev/null
+++ b/src/main/kotlin/util/mc/InitLevel.kt
@@ -0,0 +1,25 @@
+package moe.nea.firmament.util.mc
+
+enum class InitLevel {
+ STARTING,
+ MC_INIT,
+ RENDER_INIT,
+ RENDER,
+ MAIN_MENU,
+ ;
+
+ companion object {
+ var initLevel = InitLevel.STARTING
+ private set
+
+ @JvmStatic
+ fun isAtLeast(wantedLevel: InitLevel): Boolean = initLevel >= wantedLevel
+
+ @JvmStatic
+ fun bump(nextLevel: InitLevel) {
+ if (nextLevel.ordinal != initLevel.ordinal + 1)
+ error("Cannot bump initLevel $nextLevel from $initLevel")
+ initLevel = nextLevel
+ }
+ }
+}
diff --git a/src/main/kotlin/util/mc/IntrospectableItemModelManager.kt b/src/main/kotlin/util/mc/IntrospectableItemModelManager.kt
index e546fd3..537ca5b 100644
--- a/src/main/kotlin/util/mc/IntrospectableItemModelManager.kt
+++ b/src/main/kotlin/util/mc/IntrospectableItemModelManager.kt
@@ -1,7 +1,7 @@
package moe.nea.firmament.util.mc
-import net.minecraft.util.Identifier
+import net.minecraft.resources.ResourceLocation
interface IntrospectableItemModelManager {
- fun hasModel_firmament(identifier: Identifier): Boolean
+ fun hasModel_firmament(identifier: ResourceLocation): Boolean
}
diff --git a/src/main/kotlin/util/mc/InventoryUtil.kt b/src/main/kotlin/util/mc/InventoryUtil.kt
index 74f7b9f..0509138 100644
--- a/src/main/kotlin/util/mc/InventoryUtil.kt
+++ b/src/main/kotlin/util/mc/InventoryUtil.kt
@@ -2,26 +2,26 @@ package moe.nea.firmament.util.mc
import java.util.Spliterator
import java.util.Spliterators
-import net.minecraft.inventory.Inventory
-import net.minecraft.item.ItemStack
+import net.minecraft.world.Container
+import net.minecraft.world.item.ItemStack
-val Inventory.indices get() = 0 until size()
-val Inventory.iterableView
+val Container.indices get() = 0 until containerSize
+val Container.iterableView
get() = object : Iterable<ItemStack> {
override fun spliterator(): Spliterator<ItemStack> {
- return Spliterators.spliterator(iterator(), size().toLong(), 0)
+ return Spliterators.spliterator(iterator(), containerSize.toLong(), 0)
}
override fun iterator(): Iterator<ItemStack> {
return object : Iterator<ItemStack> {
var i = 0
override fun hasNext(): Boolean {
- return i < size()
+ return i < containerSize
}
override fun next(): ItemStack {
if (!hasNext()) throw NoSuchElementException()
- return getStack(i++)
+ return getItem(i++)
}
}
}
diff --git a/src/main/kotlin/util/mc/ItemUtil.kt b/src/main/kotlin/util/mc/ItemUtil.kt
index 13519cf..91b6409 100644
--- a/src/main/kotlin/util/mc/ItemUtil.kt
+++ b/src/main/kotlin/util/mc/ItemUtil.kt
@@ -1,20 +1,30 @@
package moe.nea.firmament.util.mc
-import net.minecraft.item.ItemStack
-import net.minecraft.text.Text
+import kotlin.jvm.optionals.getOrNull
+import net.minecraft.world.item.ItemStack
+import net.minecraft.nbt.CompoundTag
+import net.minecraft.nbt.NbtOps
+import net.minecraft.resources.RegistryOps
+import net.minecraft.core.HolderLookup
+import net.minecraft.network.chat.Component
+import moe.nea.firmament.util.MC
-fun ItemStack.appendLore(args: List<Text>) {
- if (args.isEmpty()) return
- modifyLore {
- val loreList = loreAccordingToNbt.toMutableList()
- for (arg in args) {
- loreList.add(arg)
- }
- loreList
- }
+fun ItemStack.appendLore(args: List<Component>) {
+ if (args.isEmpty()) return
+ modifyLore {
+ val loreList = loreAccordingToNbt.toMutableList()
+ for (arg in args) {
+ loreList.add(arg)
+ }
+ loreList
+ }
}
-fun ItemStack.modifyLore(update: (List<Text>) -> List<Text>) {
- val loreList = loreAccordingToNbt
- loreAccordingToNbt = update(loreList)
+fun ItemStack.modifyLore(update: (List<Component>) -> List<Component>) {
+ val loreList = loreAccordingToNbt
+ loreAccordingToNbt = update(loreList)
+}
+
+fun loadItemFromNbt(nbt: CompoundTag, registries: HolderLookup.Provider = MC.defaultRegistries): ItemStack? {
+ return ItemStack.CODEC.decode(RegistryOps.create(NbtOps.INSTANCE, registries), nbt).result().getOrNull()?.first
}
diff --git a/src/main/kotlin/util/mc/MCTabListAPI.kt b/src/main/kotlin/util/mc/MCTabListAPI.kt
new file mode 100644
index 0000000..56933d9
--- /dev/null
+++ b/src/main/kotlin/util/mc/MCTabListAPI.kt
@@ -0,0 +1,96 @@
+package moe.nea.firmament.util.mc
+
+import com.mojang.serialization.Codec
+import com.mojang.serialization.codecs.RecordCodecBuilder
+import java.util.Optional
+import org.jetbrains.annotations.TestOnly
+import net.minecraft.client.gui.components.PlayerTabOverlay
+import net.minecraft.nbt.NbtOps
+import net.minecraft.world.scores.PlayerTeam
+import net.minecraft.network.chat.Component
+import net.minecraft.network.chat.ComponentSerialization
+import moe.nea.firmament.annotations.Subscribe
+import moe.nea.firmament.commands.thenExecute
+import moe.nea.firmament.commands.thenLiteral
+import moe.nea.firmament.events.CommandEvent
+import moe.nea.firmament.events.TickEvent
+import moe.nea.firmament.features.debug.DeveloperFeatures
+import moe.nea.firmament.features.debug.ExportedTestConstantMeta
+import moe.nea.firmament.mixins.accessor.AccessorPlayerListHud
+import moe.nea.firmament.util.ClipboardUtils
+import moe.nea.firmament.util.MC
+import moe.nea.firmament.util.intoOptional
+import moe.nea.firmament.util.mc.SNbtFormatter.Companion.toPrettyString
+
+object MCTabListAPI {
+
+ fun PlayerTabOverlay.cast() = this as AccessorPlayerListHud
+
+ @Subscribe
+ fun onTick(event: TickEvent) {
+ _currentTabList = null
+ }
+
+ @Subscribe
+ fun devCommand(event: CommandEvent.SubCommand) {
+ event.subcommand(DeveloperFeatures.DEVELOPER_SUBCOMMAND) {
+ thenLiteral("copytablist") {
+ thenExecute {
+ currentTabList.body.forEach {
+ MC.sendChat(Component.literal(ComponentSerialization.CODEC.encodeStart(NbtOps.INSTANCE, it).orThrow.toString()))
+ }
+ var compound = CurrentTabList.CODEC.encodeStart(NbtOps.INSTANCE, currentTabList).orThrow
+ compound = ExportedTestConstantMeta.SOURCE_CODEC.encode(
+ ExportedTestConstantMeta.current,
+ NbtOps.INSTANCE,
+ compound
+ ).orThrow
+ ClipboardUtils.setTextContent(
+ compound.toPrettyString()
+ )
+ }
+ }
+ }
+ }
+
+ @get:TestOnly
+ @set:TestOnly
+ var _currentTabList: CurrentTabList? = null
+
+ val currentTabList get() = _currentTabList ?: getTabListNow().also { _currentTabList = it }
+
+ data class CurrentTabList(
+ val header: Optional<Component>,
+ val footer: Optional<Component>,
+ val body: List<Component>,
+ ) {
+ companion object {
+ val CODEC: Codec<CurrentTabList> = RecordCodecBuilder.create {
+ it.group(
+ ComponentSerialization.CODEC.optionalFieldOf("header").forGetter(CurrentTabList::header),
+ ComponentSerialization.CODEC.optionalFieldOf("footer").forGetter(CurrentTabList::footer),
+ ComponentSerialization.CODEC.listOf().fieldOf("body").forGetter(CurrentTabList::body),
+ ).apply(it, ::CurrentTabList)
+ }
+ }
+ }
+
+ private fun getTabListNow(): CurrentTabList {
+ // This is a precondition for PlayerListHud.collectEntries to be valid
+ MC.networkHandler ?: return CurrentTabList(Optional.empty(), Optional.empty(), emptyList())
+ val hud = MC.inGameHud.tabList.cast()
+ val entries = hud.collectPlayerEntries_firmament()
+ .map {
+ it.tabListDisplayName ?: run {
+ val team = it.team
+ val name = it.profile.name
+ PlayerTeam.formatNameForTeam(team, Component.literal(name))
+ }
+ }
+ return CurrentTabList(
+ header = hud.header_firmament.intoOptional(),
+ footer = hud.footer_firmament.intoOptional(),
+ body = entries,
+ )
+ }
+}
diff --git a/src/main/kotlin/util/mc/NbtItemData.kt b/src/main/kotlin/util/mc/NbtItemData.kt
index 0c49862..55bfac3 100644
--- a/src/main/kotlin/util/mc/NbtItemData.kt
+++ b/src/main/kotlin/util/mc/NbtItemData.kt
@@ -1,22 +1,22 @@
package moe.nea.firmament.util.mc
-import net.minecraft.component.DataComponentTypes
-import net.minecraft.component.type.LoreComponent
-import net.minecraft.item.ItemStack
-import net.minecraft.text.Text
+import net.minecraft.core.component.DataComponents
+import net.minecraft.world.item.component.ItemLore
+import net.minecraft.world.item.ItemStack
+import net.minecraft.network.chat.Component
-var ItemStack.loreAccordingToNbt: List<Text>
- get() = get(DataComponentTypes.LORE)?.lines ?: listOf()
+var ItemStack.loreAccordingToNbt: List<Component>
+ get() = get(DataComponents.LORE)?.lines ?: listOf()
set(value) {
- set(DataComponentTypes.LORE, LoreComponent(value))
+ set(DataComponents.LORE, ItemLore(value))
}
-var ItemStack.displayNameAccordingToNbt: Text
- get() = get(DataComponentTypes.CUSTOM_NAME) ?: get(DataComponentTypes.ITEM_NAME) ?: item.name
+var ItemStack.displayNameAccordingToNbt: Component
+ get() = get(DataComponents.CUSTOM_NAME) ?: get(DataComponents.ITEM_NAME) ?: item.name
set(value) {
- set(DataComponentTypes.CUSTOM_NAME, value)
+ set(DataComponents.CUSTOM_NAME, value)
}
-fun ItemStack.setCustomName(text: Text) {
- set(DataComponentTypes.CUSTOM_NAME, text)
+fun ItemStack.setCustomName(text: Component) {
+ set(DataComponents.CUSTOM_NAME, text)
}
diff --git a/src/main/kotlin/util/mc/NbtPrism.kt b/src/main/kotlin/util/mc/NbtPrism.kt
new file mode 100644
index 0000000..6ac7cb2
--- /dev/null
+++ b/src/main/kotlin/util/mc/NbtPrism.kt
@@ -0,0 +1,85 @@
+package moe.nea.firmament.util.mc
+
+import com.google.gson.Gson
+import com.google.gson.JsonArray
+import com.google.gson.JsonElement
+import com.google.gson.JsonPrimitive
+import com.mojang.brigadier.StringReader
+import com.mojang.brigadier.arguments.ArgumentType
+import com.mojang.brigadier.arguments.StringArgumentType
+import com.mojang.serialization.JsonOps
+import kotlin.jvm.optionals.getOrNull
+import net.minecraft.nbt.CompoundTag
+import net.minecraft.nbt.Tag
+import net.minecraft.nbt.ListTag
+import net.minecraft.nbt.NbtOps
+import net.minecraft.nbt.StringTag
+import moe.nea.firmament.util.Base64Util
+
+class NbtPrism(val path: List<String>) {
+ companion object {
+ fun fromElement(path: JsonElement): NbtPrism? {
+ if (path is JsonArray) {
+ return NbtPrism(path.map { (it as JsonPrimitive).asString })
+ } else if (path is JsonPrimitive && path.isString) {
+ return NbtPrism(path.asString.split("."))
+ }
+ return null
+ }
+ }
+
+ object Argument : ArgumentType<NbtPrism> {
+ override fun parse(reader: StringReader): NbtPrism? {
+ return fromElement(JsonPrimitive(StringArgumentType.string().parse(reader)))
+ }
+
+ override fun getExamples(): Collection<String?>? {
+ return listOf("some.nbt.path", "some.other.*", "some.path.*json.in.a.json.string")
+ }
+ }
+
+ override fun toString(): String {
+ return "Prism($path)"
+ }
+
+ fun access(root: Tag): Collection<Tag> {
+ var rootSet = mutableListOf(root)
+ var switch = mutableListOf<Tag>()
+ for (pathSegment in path) {
+ if (pathSegment == ".") continue
+ if (pathSegment != "*" && pathSegment.startsWith("*")) {
+ if (pathSegment == "*json") {
+ for (element in rootSet) {
+ val eString = element.asString().getOrNull() ?: continue
+ val element = Gson().fromJson(eString, JsonElement::class.java)
+ switch.add(JsonOps.INSTANCE.convertTo(NbtOps.INSTANCE, element))
+ }
+ } else if (pathSegment == "*base64") {
+ for (element in rootSet) {
+ val string = element.asString().getOrNull() ?: continue
+ switch.add(StringTag.valueOf(Base64Util.decodeString(string)))
+ }
+ }
+ }
+ for (element in rootSet) {
+ if (element is ListTag) {
+ if (pathSegment == "*")
+ switch.addAll(element)
+ val index = pathSegment.toIntOrNull() ?: continue
+ if (index !in element.indices) continue
+ switch.add(element[index])
+ }
+ if (element is CompoundTag) {
+ if (pathSegment == "*")
+ element.keySet().mapTo(switch) { element.get(it)!! }
+ switch.add(element.get(pathSegment) ?: continue)
+ }
+ }
+ val temp = switch
+ switch = rootSet
+ rootSet = temp
+ switch.clear()
+ }
+ return rootSet
+ }
+}
diff --git a/src/main/kotlin/util/mc/NbtUtil.kt b/src/main/kotlin/util/mc/NbtUtil.kt
new file mode 100644
index 0000000..cfd4184
--- /dev/null
+++ b/src/main/kotlin/util/mc/NbtUtil.kt
@@ -0,0 +1,15 @@
+package moe.nea.firmament.util.mc
+
+import net.minecraft.world.item.component.CustomData
+import net.minecraft.nbt.Tag
+import net.minecraft.nbt.ListTag
+import moe.nea.firmament.mixins.accessor.AccessorNbtComponent
+
+fun Iterable<Tag>.toNbtList() = ListTag().also {
+ for (element in this) {
+ it.add(element)
+ }
+}
+
+@Suppress("CAST_NEVER_SUCCEEDS")
+val CustomData.unsafeNbt get() = (this as AccessorNbtComponent).unsafeNbt_firmament
diff --git a/src/main/kotlin/util/mc/PlayerUtil.kt b/src/main/kotlin/util/mc/PlayerUtil.kt
new file mode 100644
index 0000000..7c21987
--- /dev/null
+++ b/src/main/kotlin/util/mc/PlayerUtil.kt
@@ -0,0 +1,7 @@
+package moe.nea.firmament.util.mc
+
+import net.minecraft.world.entity.EquipmentSlot
+import net.minecraft.world.entity.player.Player
+
+
+val Player.mainHandStack get() = this.getItemBySlot(EquipmentSlot.MAINHAND)
diff --git a/src/main/kotlin/util/mc/Rectangle.kt b/src/main/kotlin/util/mc/Rectangle.kt
new file mode 100644
index 0000000..6495c29
--- /dev/null
+++ b/src/main/kotlin/util/mc/Rectangle.kt
@@ -0,0 +1,11 @@
+package moe.nea.firmament.util.mc
+
+import me.shedaniel.math.Rectangle
+import net.minecraft.client.gui.navigation.ScreenAxis
+import net.minecraft.client.gui.navigation.ScreenRectangle
+
+fun Rectangle.asScreenRectangle() =
+ ScreenRectangle.of(
+ ScreenAxis.HORIZONTAL,
+ x, y, width, height
+ )
diff --git a/src/main/kotlin/util/mc/SNbtFormatter.kt b/src/main/kotlin/util/mc/SNbtFormatter.kt
index e773927..0e630eb 100644
--- a/src/main/kotlin/util/mc/SNbtFormatter.kt
+++ b/src/main/kotlin/util/mc/SNbtFormatter.kt
@@ -1,22 +1,23 @@
package moe.nea.firmament.util.mc
-import net.minecraft.nbt.NbtByte
-import net.minecraft.nbt.NbtByteArray
-import net.minecraft.nbt.NbtCompound
-import net.minecraft.nbt.NbtDouble
-import net.minecraft.nbt.NbtElement
-import net.minecraft.nbt.NbtEnd
-import net.minecraft.nbt.NbtFloat
-import net.minecraft.nbt.NbtInt
-import net.minecraft.nbt.NbtIntArray
-import net.minecraft.nbt.NbtList
-import net.minecraft.nbt.NbtLong
-import net.minecraft.nbt.NbtLongArray
-import net.minecraft.nbt.NbtShort
-import net.minecraft.nbt.NbtString
-import net.minecraft.nbt.visitor.NbtElementVisitor
-
-class SNbtFormatter private constructor() : NbtElementVisitor {
+import net.minecraft.nbt.CollectionTag
+import net.minecraft.nbt.ByteTag
+import net.minecraft.nbt.ByteArrayTag
+import net.minecraft.nbt.CompoundTag
+import net.minecraft.nbt.DoubleTag
+import net.minecraft.nbt.Tag
+import net.minecraft.nbt.EndTag
+import net.minecraft.nbt.FloatTag
+import net.minecraft.nbt.IntTag
+import net.minecraft.nbt.IntArrayTag
+import net.minecraft.nbt.ListTag
+import net.minecraft.nbt.LongTag
+import net.minecraft.nbt.LongArrayTag
+import net.minecraft.nbt.ShortTag
+import net.minecraft.nbt.StringTag
+import net.minecraft.nbt.TagVisitor
+
+class SNbtFormatter private constructor() : TagVisitor {
private val result = StringBuilder()
private var indent = 0
private fun writeIndent() {
@@ -31,52 +32,52 @@ class SNbtFormatter private constructor() : NbtElementVisitor {
indent--
}
- fun apply(element: NbtElement): StringBuilder {
+ fun apply(element: Tag): StringBuilder {
element.accept(this)
return result
}
- override fun visitString(element: NbtString) {
- result.append(NbtString.escape(element.asString()))
+ override fun visitString(element: StringTag) {
+ result.append(StringTag.quoteAndEscape(element.value))
}
- override fun visitByte(element: NbtByte) {
- result.append(element.numberValue()).append("b")
+ override fun visitByte(element: ByteTag) {
+ result.append(element.box()).append("b")
}
- override fun visitShort(element: NbtShort) {
+ override fun visitShort(element: ShortTag) {
result.append(element.shortValue()).append("s")
}
- override fun visitInt(element: NbtInt) {
+ override fun visitInt(element: IntTag) {
result.append(element.intValue())
}
- override fun visitLong(element: NbtLong) {
+ override fun visitLong(element: LongTag) {
result.append(element.longValue()).append("L")
}
- override fun visitFloat(element: NbtFloat) {
+ override fun visitFloat(element: FloatTag) {
result.append(element.floatValue()).append("f")
}
- override fun visitDouble(element: NbtDouble) {
+ override fun visitDouble(element: DoubleTag) {
result.append(element.doubleValue()).append("d")
}
- private fun visitArrayContents(array: List<NbtElement>) {
+ private fun visitArrayContents(array: CollectionTag) {
array.forEachIndexed { index, element ->
writeIndent()
element.accept(this)
- if (array.size != index + 1) {
+ if (array.size() != index + 1) {
result.append(",")
}
result.append("\n")
}
}
- private fun writeArray(arrayTypeTag: String, array: List<NbtElement>) {
+ private fun writeArray(arrayTypeTag: String, array: CollectionTag) {
result.append("[").append(arrayTypeTag).append("\n")
pushIndent()
visitArrayContents(array)
@@ -86,30 +87,30 @@ class SNbtFormatter private constructor() : NbtElementVisitor {
}
- override fun visitByteArray(element: NbtByteArray) {
+ override fun visitByteArray(element: ByteArrayTag) {
writeArray("B;", element)
}
- override fun visitIntArray(element: NbtIntArray) {
+ override fun visitIntArray(element: IntArrayTag) {
writeArray("I;", element)
}
- override fun visitLongArray(element: NbtLongArray) {
+ override fun visitLongArray(element: LongArrayTag) {
writeArray("L;", element)
}
- override fun visitList(element: NbtList) {
+ override fun visitList(element: ListTag) {
writeArray("", element)
}
- override fun visitCompound(compound: NbtCompound) {
+ override fun visitCompound(compound: CompoundTag) {
result.append("{\n")
pushIndent()
- val keys = compound.keys.sorted()
+ val keys = compound.keySet().sorted()
keys.forEachIndexed { index, key ->
writeIndent()
val element = compound[key] ?: error("Key '$key' found but not present in compound: $compound")
- val escapedName = if (key.matches(SIMPLE_NAME)) key else NbtString.escape(key)
+ val escapedName = escapeName(key)
result.append(escapedName).append(": ")
element.accept(this)
if (keys.size != index + 1) {
@@ -122,17 +123,20 @@ class SNbtFormatter private constructor() : NbtElementVisitor {
result.append("}")
}
- override fun visitEnd(element: NbtEnd) {
+ override fun visitEnd(element: EndTag) {
result.append("END")
}
companion object {
- fun prettify(nbt: NbtElement): String {
+ fun prettify(nbt: Tag): String {
return SNbtFormatter().apply(nbt).toString()
}
- fun NbtElement.toPrettyString() = prettify(this)
+ fun Tag.toPrettyString() = prettify(this)
- private val SIMPLE_NAME = "[A-Za-z0-9._+-]+".toRegex()
+ fun escapeName(key: String): String =
+ if (key.matches(SIMPLE_NAME)) key else StringTag.quoteAndEscape(key)
+
+ val SIMPLE_NAME = "[A-Za-z0-9._+-]+".toRegex()
}
}
diff --git a/src/main/kotlin/util/mc/ScreenUtil.kt b/src/main/kotlin/util/mc/ScreenUtil.kt
index 36feb6b..4e3dbf1 100644
--- a/src/main/kotlin/util/mc/ScreenUtil.kt
+++ b/src/main/kotlin/util/mc/ScreenUtil.kt
@@ -1,9 +1,9 @@
package moe.nea.firmament.util.mc
-import net.minecraft.client.gui.screen.Screen
-import net.minecraft.client.gui.screen.ingame.HandledScreen
-import net.minecraft.entity.player.PlayerInventory
-import net.minecraft.screen.slot.Slot
+import net.minecraft.client.gui.screens.Screen
+import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen
+import net.minecraft.world.entity.player.Inventory
+import net.minecraft.world.inventory.Slot
object ScreenUtil {
private var lastScreen: Screen? = null
@@ -12,15 +12,15 @@ object ScreenUtil {
data class SlotIndex(val index: Int, val isPlayerInventory: Boolean)
fun Screen.getSlotsByIndex(): Map<SlotIndex, Slot> {
- if (this !is HandledScreen<*>) return mapOf()
+ if (this !is AbstractContainerScreen<*>) return mapOf()
if (lastScreen === this) return slotsByIndex
lastScreen = this
- slotsByIndex = this.screenHandler.slots.associate {
- SlotIndex(it.index, it.inventory is PlayerInventory) to it
+ slotsByIndex = this.menu.slots.associate {
+ SlotIndex(it.containerSlot, it.container is Inventory) to it
}
return slotsByIndex
}
- fun Screen.getSlotByIndex( index: Int, isPlayerInventory: Boolean): Slot? =
+ fun Screen.getSlotByIndex(index: Int, isPlayerInventory: Boolean): Slot? =
getSlotsByIndex()[SlotIndex(index, isPlayerInventory)]
}
diff --git a/src/main/kotlin/util/mc/SkullItemData.kt b/src/main/kotlin/util/mc/SkullItemData.kt
index 0405b65..80028af 100644
--- a/src/main/kotlin/util/mc/SkullItemData.kt
+++ b/src/main/kotlin/util/mc/SkullItemData.kt
@@ -2,19 +2,20 @@
package moe.nea.firmament.util.mc
+import com.google.common.collect.Multimap
+import com.google.common.collect.Multimaps
import com.mojang.authlib.GameProfile
import com.mojang.authlib.minecraft.MinecraftProfileTexture
import com.mojang.authlib.properties.Property
+import com.mojang.authlib.properties.PropertyMap
+import java.time.Instant
import java.util.UUID
-import kotlinx.datetime.Clock
-import kotlinx.datetime.Instant
import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers
-import kotlinx.serialization.encodeToString
-import net.minecraft.component.DataComponentTypes
-import net.minecraft.component.type.ProfileComponent
-import net.minecraft.item.ItemStack
-import net.minecraft.item.Items
+import net.minecraft.core.component.DataComponents
+import net.minecraft.world.item.component.ResolvableProfile
+import net.minecraft.world.item.ItemStack
+import net.minecraft.world.item.Items
import moe.nea.firmament.Firmament
import moe.nea.firmament.util.Base64Util.padToValidBase64
import moe.nea.firmament.util.assertTrueOr
@@ -23,66 +24,75 @@ import moe.nea.firmament.util.json.InstantAsLongSerializer
@Serializable
data class MinecraftProfileTextureKt(
- val url: String,
- val metadata: Map<String, String> = mapOf(),
+ val url: String,
+ val metadata: Map<String, String> = mapOf(),
)
@Serializable
data class MinecraftTexturesPayloadKt(
- val textures: Map<MinecraftProfileTexture.Type, MinecraftProfileTextureKt> = mapOf(),
- val profileId: UUID? = null,
- val profileName: String? = null,
- val isPublic: Boolean = true,
- val timestamp: Instant = Clock.System.now(),
+ val textures: Map<MinecraftProfileTexture.Type, MinecraftProfileTextureKt> = mapOf(),
+ val profileId: UUID? = null,
+ val profileName: String? = null,
+ val isPublic: Boolean = true,
+ val timestamp: Instant = Instant.now(),
)
-fun GameProfile.setTextures(textures: MinecraftTexturesPayloadKt) {
- val json = Firmament.json.encodeToString(textures)
- val encoded = java.util.Base64.getEncoder().encodeToString(json.encodeToByteArray())
- properties.put(propertyTextures, Property(propertyTextures, encoded))
+fun createSkullTextures(textures: MinecraftTexturesPayloadKt): PropertyMap {
+ val json = Firmament.json.encodeToString(textures)
+ val encoded = java.util.Base64.getEncoder().encodeToString(json.encodeToByteArray())
+ return PropertyMap(
+ Multimaps.forMap(mapOf(propertyTextures to Property(propertyTextures, encoded)))
+ )
}
private val propertyTextures = "textures"
fun ItemStack.setEncodedSkullOwner(uuid: UUID, encodedData: String) {
- assert(this.item == Items.PLAYER_HEAD)
- val gameProfile = GameProfile(uuid, "LameGuy123")
- gameProfile.properties.put(propertyTextures, Property(propertyTextures, encodedData.padToValidBase64()))
- this.set(DataComponentTypes.PROFILE, ProfileComponent(gameProfile))
+ assert(this.item == Items.PLAYER_HEAD)
+ val gameProfile = GameProfile(
+ uuid, "LameGuy123",
+ PropertyMap(
+ Multimaps.forMap(
+ mapOf(propertyTextures to Property(propertyTextures, encodedData.padToValidBase64()))
+ )
+ )
+ )
+ this.set(DataComponents.PROFILE, ResolvableProfile.createResolved(gameProfile))
}
-val zeroUUID = UUID.fromString("d3cb85e2-3075-48a1-b213-a9bfb62360c1")
+val arbitraryUUID = UUID.fromString("d3cb85e2-3075-48a1-b213-a9bfb62360c1")
fun createSkullItem(uuid: UUID, url: String) = ItemStack(Items.PLAYER_HEAD)
- .also { it.setSkullOwner(uuid, url) }
+ .also { it.setSkullOwner(uuid, url) }
fun ItemStack.setSkullOwner(uuid: UUID, url: String) {
- assert(this.item == Items.PLAYER_HEAD)
- val gameProfile = GameProfile(uuid, "nea89")
- gameProfile.setTextures(
- MinecraftTexturesPayloadKt(
- textures = mapOf(MinecraftProfileTexture.Type.SKIN to MinecraftProfileTextureKt(url)),
- profileId = uuid,
- profileName = "nea89",
- )
- )
- this.set(DataComponentTypes.PROFILE, ProfileComponent(gameProfile))
+ assert(this.item == Items.PLAYER_HEAD)
+ val gameProfile = GameProfile(
+ uuid, "nea89", createSkullTextures(
+ MinecraftTexturesPayloadKt(
+ textures = mapOf(MinecraftProfileTexture.Type.SKIN to MinecraftProfileTextureKt(url)),
+ profileId = uuid,
+ profileName = "nea89",
+ )
+ )
+ )
+ this.set(DataComponents.PROFILE, ResolvableProfile.createResolved(gameProfile))
}
fun decodeProfileTextureProperty(property: Property): MinecraftTexturesPayloadKt? {
- assertTrueOr(property.name == propertyTextures) { return null }
- return try {
- var encodedF: String = property.value
- while (encodedF.length % 4 != 0 && encodedF.last() == '=') {
- encodedF = encodedF.substring(0, encodedF.length - 1)
- }
- val json = java.util.Base64.getDecoder().decode(encodedF).decodeToString()
- Firmament.json.decodeFromString<MinecraftTexturesPayloadKt>(json)
- } catch (e: Exception) {
- // Malformed profile data
- if (Firmament.DEBUG)
- e.printStackTrace()
- null
- }
+ assertTrueOr(property.name == propertyTextures) { return null }
+ return try {
+ var encodedF: String = property.value
+ while (encodedF.length % 4 != 0 && encodedF.last() == '=') {
+ encodedF = encodedF.substring(0, encodedF.length - 1)
+ }
+ val json = java.util.Base64.getDecoder().decode(encodedF).decodeToString()
+ Firmament.json.decodeFromString<MinecraftTexturesPayloadKt>(json)
+ } catch (e: Exception) {
+ // Malformed profile data
+ if (Firmament.DEBUG)
+ e.printStackTrace()
+ null
+ }
}
diff --git a/src/main/kotlin/util/mc/SlotUtils.kt b/src/main/kotlin/util/mc/SlotUtils.kt
index 4709dcf..2f5fd49 100644
--- a/src/main/kotlin/util/mc/SlotUtils.kt
+++ b/src/main/kotlin/util/mc/SlotUtils.kt
@@ -1,34 +1,46 @@
package moe.nea.firmament.util.mc
-import net.minecraft.screen.ScreenHandler
-import net.minecraft.screen.slot.Slot
-import net.minecraft.screen.slot.SlotActionType
+import org.lwjgl.glfw.GLFW
+import net.minecraft.world.inventory.AbstractContainerMenu
+import net.minecraft.world.inventory.Slot
+import net.minecraft.world.inventory.ClickType
import moe.nea.firmament.util.MC
object SlotUtils {
- fun Slot.clickMiddleMouseButton(handler: ScreenHandler) {
- MC.interactionManager?.clickSlot(
- handler.syncId,
- this.id,
- 2,
- SlotActionType.CLONE,
+ fun Slot.clickMiddleMouseButton(handler: AbstractContainerMenu) {
+ MC.interactionManager?.handleInventoryMouseClick(
+ handler.containerId,
+ this.index,
+ GLFW.GLFW_MOUSE_BUTTON_MIDDLE,
+ ClickType.CLONE,
MC.player
)
}
- fun Slot.swapWithHotBar(handler: ScreenHandler, hotbarIndex: Int) {
- MC.interactionManager?.clickSlot(
- handler.syncId, this.id,
- hotbarIndex, SlotActionType.SWAP,
- MC.player)
+ fun Slot.swapWithHotBar(handler: AbstractContainerMenu, hotbarIndex: Int) {
+ MC.interactionManager?.handleInventoryMouseClick(
+ handler.containerId, this.index,
+ hotbarIndex, ClickType.SWAP,
+ MC.player
+ )
+ }
+
+ fun Slot.clickRightMouseButton(handler: AbstractContainerMenu) {
+ MC.interactionManager?.handleInventoryMouseClick(
+ handler.containerId,
+ this.index,
+ GLFW.GLFW_MOUSE_BUTTON_RIGHT,
+ ClickType.PICKUP,
+ MC.player
+ )
}
- fun Slot.clickRightMouseButton(handler: ScreenHandler) {
- MC.interactionManager?.clickSlot(
- handler.syncId,
- this.id,
- 1,
- SlotActionType.PICKUP,
+ fun Slot.clickLeftMouseButton(handler: AbstractContainerMenu) {
+ MC.interactionManager?.handleInventoryMouseClick(
+ handler.containerId,
+ this.index,
+ GLFW.GLFW_MOUSE_BUTTON_LEFT,
+ ClickType.PICKUP,
MC.player
)
}
diff --git a/src/main/kotlin/util/mc/TolerantRegistriesOps.kt b/src/main/kotlin/util/mc/TolerantRegistriesOps.kt
index ce596a0..833aca9 100644
--- a/src/main/kotlin/util/mc/TolerantRegistriesOps.kt
+++ b/src/main/kotlin/util/mc/TolerantRegistriesOps.kt
@@ -2,27 +2,27 @@ 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
+import net.minecraft.core.Registry
+import net.minecraft.resources.ResourceKey
+import net.minecraft.resources.RegistryOps
+import net.minecraft.core.HolderLookup
+import net.minecraft.core.HolderOwner
class TolerantRegistriesOps<T>(
delegate: DynamicOps<T>,
- registryInfoGetter: RegistryInfoGetter
+ registryInfoGetter: RegistryInfoLookup
) : RegistryOps<T>(delegate, registryInfoGetter) {
- constructor(delegate: DynamicOps<T>, registry: RegistryWrapper.WrapperLookup) :
- this(delegate, CachedRegistryInfoGetter(registry))
+ constructor(delegate: DynamicOps<T>, registry: HolderLookup.Provider) :
+ this(delegate, HolderLookupAdapter(registry))
- class TolerantOwner<E> : RegistryEntryOwner<E> {
- override fun ownerEquals(other: RegistryEntryOwner<E>?): Boolean {
+ class TolerantOwner<E> : HolderOwner<E> {
+ override fun canSerializeIn(other: HolderOwner<E>?): Boolean {
return true
}
}
- override fun <E : Any?> getOwner(registryRef: RegistryKey<out Registry<out E>>?): Optional<RegistryEntryOwner<E>> {
- return super.getOwner(registryRef).map {
+ override fun <E : Any?> owner(registryRef: ResourceKey<out Registry<out E>>?): Optional<HolderOwner<E>> {
+ return super.owner(registryRef).map {
TolerantOwner()
}
}
diff --git a/src/main/kotlin/util/mc/asFakeServer.kt b/src/main/kotlin/util/mc/asFakeServer.kt
index d3811bd..1075d62 100644
--- a/src/main/kotlin/util/mc/asFakeServer.kt
+++ b/src/main/kotlin/util/mc/asFakeServer.kt
@@ -1,27 +1,27 @@
package moe.nea.firmament.util.mc
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource
-import net.minecraft.server.command.CommandOutput
-import net.minecraft.server.command.ServerCommandSource
-import net.minecraft.text.Text
+import net.minecraft.commands.CommandSource
+import net.minecraft.commands.CommandSourceStack
+import net.minecraft.network.chat.Component
-fun FabricClientCommandSource.asFakeServer(): ServerCommandSource {
+fun FabricClientCommandSource.asFakeServer(): CommandSourceStack {
val source = this
- return ServerCommandSource(
- object : CommandOutput {
- override fun sendMessage(message: Text?) {
- source.player.sendMessage(message, false)
+ return CommandSourceStack(
+ object : CommandSource {
+ override fun sendSystemMessage(message: Component?) {
+ source.player.displayClientMessage(message, false)
}
- override fun shouldReceiveFeedback(): Boolean {
+ override fun acceptsSuccess(): Boolean {
return true
}
- override fun shouldTrackOutput(): Boolean {
+ override fun acceptsFailure(): Boolean {
return true
}
- override fun shouldBroadcastConsoleToOps(): Boolean {
+ override fun shouldInformAdmins(): Boolean {
return true
}
},
@@ -30,7 +30,7 @@ fun FabricClientCommandSource.asFakeServer(): ServerCommandSource {
null,
0,
"FakeServerCommandSource",
- Text.literal("FakeServerCommandSource"),
+ Component.literal("FakeServerCommandSource"),
null,
source.player
)
diff --git a/src/main/kotlin/util/net/HttpUtil.kt b/src/main/kotlin/util/net/HttpUtil.kt
new file mode 100644
index 0000000..1b810e3
--- /dev/null
+++ b/src/main/kotlin/util/net/HttpUtil.kt
@@ -0,0 +1,86 @@
+package moe.nea.firmament.util.net
+
+import java.io.InputStream
+import java.net.URI
+import java.net.URL
+import java.net.http.HttpClient
+import java.net.http.HttpRequest
+import java.net.http.HttpResponse
+import java.nio.ByteBuffer
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.CompletionStage
+import java.util.concurrent.Flow
+import kotlinx.serialization.DeserializationStrategy
+import kotlinx.serialization.json.decodeFromStream
+import kotlinx.serialization.serializer
+import moe.nea.firmament.Firmament
+
+object HttpUtil {
+ val httpClient = HttpClient.newBuilder()
+ .followRedirects(HttpClient.Redirect.NORMAL)
+ .build()
+
+ data class Request(val request: HttpRequest.Builder) {
+ fun <T> execute(bodyHandler: HttpResponse.BodyHandler<T>): CompletableFuture<HttpResponse<T>> {
+ return httpClient.sendAsync(request.build(), bodyHandler)
+ }
+
+ fun <T> forBody(bodyHandler: HttpResponse.BodyHandler<T>): CompletableFuture<T> {
+ return execute(bodyHandler).thenApply { it.body() }
+ }
+
+ fun forInputStream(): CompletableFuture<InputStream> {
+ return forBody(HttpResponse.BodyHandlers.ofInputStream())
+ }
+
+ inline fun <reified T> forJson(): CompletableFuture<T> {
+ return forJson(serializer())
+ }
+
+ fun <T> forJson(serializer: DeserializationStrategy<T>): CompletableFuture<T> {
+ return forBody(jsonBodyHandler(serializer))
+ }
+
+ fun header(key: String, value: String) {
+ request.header(key, value)
+ }
+ }
+
+ fun <T> jsonBodyHandler(serializer: DeserializationStrategy<T>): HttpResponse.BodyHandler<T> {
+ val inp = HttpResponse.BodyHandlers.ofInputStream()
+ return HttpResponse.BodyHandler {
+ val subscriber = inp.apply(it)
+ object : HttpResponse.BodySubscriber<T> {
+ override fun getBody(): CompletionStage<T> {
+ return subscriber.body.thenApply { Firmament.json.decodeFromStream(serializer, it) }
+ }
+
+ override fun onSubscribe(subscription: Flow.Subscription?) {
+ subscriber.onSubscribe(subscription)
+ }
+
+ override fun onNext(item: List<ByteBuffer?>?) {
+ subscriber.onNext(item)
+ }
+
+ override fun onError(throwable: Throwable?) {
+ subscriber.onError(throwable)
+ }
+
+ override fun onComplete() {
+ subscriber.onComplete()
+ }
+ }
+ }
+ }
+
+ fun request(url: String): Request = request(URI.create(url))
+ fun request(url: URL): Request = request(url.toURI())
+ fun request(url: URI): Request {
+ return Request(
+ HttpRequest.newBuilder(url)
+ .GET()
+ .header("user-agent", "Firmament/${Firmament.version}")
+ )
+ }
+}
diff --git a/src/main/kotlin/util/regex.kt b/src/main/kotlin/util/regex.kt
index f239810..be6bcfb 100644
--- a/src/main/kotlin/util/regex.kt
+++ b/src/main/kotlin/util/regex.kt
@@ -26,6 +26,13 @@ inline fun <T> Pattern.useMatch(string: String?, block: Matcher.() -> T): T? {
?.let(block)
}
+fun <T> String.ifDropLast(suffix: String, block: (String) -> T): T? {
+ if (endsWith(suffix)) {
+ return block(dropLast(suffix.length))
+ }
+ return null
+}
+
@Language("RegExp")
val TIME_PATTERN = "[0-9]+[ms]"
diff --git a/src/main/kotlin/util/render/CustomRenderLayers.kt b/src/main/kotlin/util/render/CustomRenderLayers.kt
new file mode 100644
index 0000000..4a85c17
--- /dev/null
+++ b/src/main/kotlin/util/render/CustomRenderLayers.kt
@@ -0,0 +1,105 @@
+package util.render
+
+import com.mojang.blaze3d.pipeline.BlendFunction
+import com.mojang.blaze3d.pipeline.RenderPipeline
+import com.mojang.blaze3d.platform.DepthTestFunction
+import com.mojang.blaze3d.vertex.VertexFormat.Mode
+import java.util.function.Function
+import net.minecraft.client.renderer.RenderPipelines
+import com.mojang.blaze3d.shaders.UniformType
+import net.minecraft.client.renderer.RenderType
+import net.minecraft.client.renderer.RenderStateShard
+import com.mojang.blaze3d.vertex.DefaultVertexFormat
+import net.minecraft.resources.ResourceLocation
+import net.minecraft.Util
+import moe.nea.firmament.Firmament
+
+object CustomRenderPipelines {
+ val GUI_TEXTURED_NO_DEPTH_TRIS =
+ RenderPipeline.builder(RenderPipelines.GUI_TEXTURED_SNIPPET)
+ .withVertexFormat(DefaultVertexFormat.POSITION_TEX_COLOR, Mode.TRIANGLES)
+ .withLocation(Firmament.identifier("gui_textured_overlay_tris"))
+ .withDepthTestFunction(DepthTestFunction.NO_DEPTH_TEST)
+ .withCull(false)
+ .withDepthWrite(false)
+ .build()
+ val OMNIPRESENT_LINES = RenderPipeline
+ .builder(RenderPipelines.LINES_SNIPPET)
+ .withLocation(Firmament.identifier("lines"))
+ .withDepthWrite(false)
+ .withDepthTestFunction(DepthTestFunction.NO_DEPTH_TEST)
+ .build()
+ val COLORED_OMNIPRESENT_QUADS =
+ RenderPipeline.builder(RenderPipelines.MATRICES_PROJECTION_SNIPPET)// TODO: split this up to support better transparent ordering.
+ .withLocation(Firmament.identifier("colored_omnipresent_quads"))
+ .withVertexShader("core/position_color")
+ .withFragmentShader("core/position_color")
+ .withVertexFormat(DefaultVertexFormat.POSITION_COLOR, Mode.QUADS)
+ .withDepthTestFunction(DepthTestFunction.NO_DEPTH_TEST)
+ .withCull(false)
+ .withDepthWrite(false)
+ .withBlend(BlendFunction.TRANSLUCENT)
+ .build()
+
+ val CIRCLE_FILTER_TRANSLUCENT_GUI_TRIS =
+ RenderPipeline.builder(RenderPipelines.GUI_TEXTURED_SNIPPET)
+ .withVertexFormat(DefaultVertexFormat.POSITION_TEX_COLOR, Mode.TRIANGLES)
+ .withLocation(Firmament.identifier("gui_textured_overlay_tris_circle"))
+ .withUniform("CutoutRadius", UniformType.UNIFORM_BUFFER)
+ .withFragmentShader(Firmament.identifier("circle_discard_color"))
+// .withBlend(BlendFunction.TRANSLUCENT)
+ .build()
+ val PARALLAX_CAPE_SHADER =
+ RenderPipeline.builder(RenderPipelines.ENTITY_SNIPPET)
+ .withLocation(Firmament.identifier("parallax_cape"))
+ .withFragmentShader(Firmament.identifier("cape/parallax"))
+ .withSampler("Sampler0")
+ .withSampler("Sampler1")
+ .withSampler("Sampler3")
+ .withUniform("Animation", UniformType.UNIFORM_BUFFER)
+ .build()
+}
+
+object CustomRenderLayers {
+ inline fun memoizeTextured(crossinline func: (ResourceLocation) -> RenderType.CompositeRenderType) = memoize(func)
+ inline fun <T, R> memoize(crossinline func: (T) -> R): Function<T, R> {
+ return Util.memoize { it: T -> func(it) }
+ }
+
+ val GUI_TEXTURED_NO_DEPTH_TRIS = memoizeTextured { texture ->
+ RenderType.create(
+ "firmament_gui_textured_overlay_tris",
+ RenderType.TRANSIENT_BUFFER_SIZE,
+ CustomRenderPipelines.GUI_TEXTURED_NO_DEPTH_TRIS,
+ RenderType.CompositeState.builder().setTextureState(
+ RenderStateShard.TextureStateShard(texture, false)
+ )
+ .createCompositeState(false)
+ )
+ }
+ val LINES = RenderType.create(
+ "firmament_lines",
+ RenderType.TRANSIENT_BUFFER_SIZE,
+ CustomRenderPipelines.OMNIPRESENT_LINES,
+ RenderType.CompositeState.builder() // TODO: accept linewidth here
+ .createCompositeState(false)
+ )
+ val COLORED_QUADS = RenderType.create(
+ "firmament_quads",
+ RenderType.TRANSIENT_BUFFER_SIZE,
+ false, true,
+ CustomRenderPipelines.COLORED_OMNIPRESENT_QUADS,
+ RenderType.CompositeState.builder()
+ .setLightmapState(RenderStateShard.NO_LIGHTMAP)
+ .createCompositeState(false)
+ )
+
+ val TRANSLUCENT_CIRCLE_GUI =
+ RenderType.create(
+ "firmament_circle_gui",
+ RenderType.TRANSIENT_BUFFER_SIZE,
+ CustomRenderPipelines.CIRCLE_FILTER_TRANSLUCENT_GUI_TRIS,
+ RenderType.CompositeState.builder()
+ .createCompositeState(false)
+ )
+}
diff --git a/src/main/kotlin/util/render/DrawContextExt.kt b/src/main/kotlin/util/render/DrawContextExt.kt
index a143d4d..9ef66f3 100644
--- a/src/main/kotlin/util/render/DrawContextExt.kt
+++ b/src/main/kotlin/util/render/DrawContextExt.kt
@@ -2,63 +2,33 @@ package moe.nea.firmament.util.render
import com.mojang.blaze3d.systems.RenderSystem
import me.shedaniel.math.Color
-import org.joml.Matrix4f
-import net.minecraft.client.gui.DrawContext
-import net.minecraft.client.render.RenderLayer
-import net.minecraft.client.render.RenderLayer.MultiPhaseParameters
-import net.minecraft.client.render.RenderPhase
-import net.minecraft.client.render.VertexFormat
-import net.minecraft.client.render.VertexFormat.DrawMode
-import net.minecraft.client.render.VertexFormats
-import net.minecraft.util.Identifier
-import net.minecraft.util.TriState
-import net.minecraft.util.Util
+import org.joml.Vector3f
+import util.render.CustomRenderLayers
+import kotlin.math.abs
+import net.minecraft.client.renderer.RenderPipelines
+import net.minecraft.client.gui.GuiGraphics
+import net.minecraft.client.gui.navigation.ScreenRectangle
+import net.minecraft.client.renderer.MultiBufferSource
+import com.mojang.blaze3d.vertex.PoseStack
+import net.minecraft.resources.ResourceLocation
import moe.nea.firmament.util.MC
-fun DrawContext.isUntranslatedGuiDrawContext(): Boolean {
- return (matrices.peek().positionMatrix.properties() and Matrix4f.PROPERTY_IDENTITY.toInt()) != 0
-}
-
-object GuiRenderLayers {
- val GUI_TEXTURED_NO_DEPTH = Util.memoize<Identifier, RenderLayer> { texture: Identifier ->
- RenderLayer.of("firmament_gui_textured_no_depth",
- VertexFormats.POSITION_TEXTURE_COLOR,
- DrawMode.QUADS,
- DEFAULT_BUFFER_SIZE,
- MultiPhaseParameters.builder()
- .texture(RenderPhase.Texture(texture, TriState.FALSE, false))
- .program(RenderPhase.POSITION_TEXTURE_COLOR_PROGRAM)
- .transparency(RenderPhase.TRANSLUCENT_TRANSPARENCY)
- .depthTest(RenderPhase.ALWAYS_DEPTH_TEST)
- .build(false))
- }
- val GUI_TEXTURED_TRIS = Util.memoize { texture: Identifier ->
- RenderLayer.of("firmament_gui_textured_overlay_tris",
- VertexFormats.POSITION_TEXTURE_COLOR,
- DrawMode.TRIANGLES,
- DEFAULT_BUFFER_SIZE,
- MultiPhaseParameters.builder()
- .texture(RenderPhase.Texture(texture, TriState.DEFAULT, false))
- .program(RenderPhase.POSITION_TEXTURE_COLOR_PROGRAM)
- .transparency(RenderPhase.TRANSLUCENT_TRANSPARENCY)
- .depthTest(RenderPhase.ALWAYS_DEPTH_TEST)
- .writeMaskState(RenderPhase.COLOR_MASK)
- .build(false))
- }
+fun GuiGraphics.isUntranslatedGuiDrawContext(): Boolean {
+ return pose().m00 == 1F && pose().m11 == 1f && pose().m01 == 0F && pose().m10 == 0F && pose().m20 == 0F && pose().m21 == 0F
}
@Deprecated("Use the other drawGuiTexture")
-fun DrawContext.drawGuiTexture(
- x: Int, y: Int, z: Int, width: Int, height: Int, sprite: Identifier
-) = this.drawGuiTexture(RenderLayer::getGuiTextured, sprite, x, y, width, height)
+fun GuiGraphics.drawGuiTexture(
+ x: Int, y: Int, z: Int, width: Int, height: Int, sprite: ResourceLocation
+) = this.blitSprite(RenderPipelines.GUI_TEXTURED, sprite, x, y, width, height)
-fun DrawContext.drawGuiTexture(
- sprite: Identifier,
+fun GuiGraphics.drawGuiTexture(
+ sprite: ResourceLocation,
x: Int, y: Int, width: Int, height: Int
-) = this.drawGuiTexture(RenderLayer::getGuiTextured, sprite, x, y, width, height)
+) = this.blitSprite(RenderPipelines.GUI_TEXTURED, sprite, x, y, width, height)
-fun DrawContext.drawTexture(
- sprite: Identifier,
+fun GuiGraphics.drawTexture(
+ sprite: ResourceLocation,
x: Int,
y: Int,
u: Float,
@@ -68,34 +38,130 @@ fun DrawContext.drawTexture(
textureWidth: Int,
textureHeight: Int
) {
- this.drawTexture(RenderLayer::getGuiTextured,
- sprite,
- x,
- y,
- u,
- v,
- width,
- height,
- width,
- height,
- textureWidth,
- textureHeight)
+ this.blit(
+ RenderPipelines.GUI_TEXTURED,
+ sprite,
+ x,
+ y,
+ u,
+ v,
+ width,
+ height,
+ width,
+ height,
+ textureWidth,
+ textureHeight
+ )
+}
+
+data class LineRenderState(
+ override val x1: Int,
+ override val x2: Int,
+ override val y1: Int,
+ override val y2: Int,
+ override val scale: Float,
+ override val bounds: ScreenRectangle,
+ val lineWidth: Float,
+ val w: Int,
+ val h: Int,
+ val color: Int,
+ val direction: LineDirection,
+) : MultiSpecialGuiRenderState() {
+ enum class LineDirection {
+ TOP_LEFT_TO_BOTTOM_RIGHT,
+ BOTTOM_LEFT_TO_TOP_RIGHT,
+ }
+
+ override fun createRenderer(vertexConsumers: MultiBufferSource.BufferSource): MultiSpecialGuiRenderer<out MultiSpecialGuiRenderState> {
+ return LineRenderer(vertexConsumers)
+ }
+
+ override val scissorArea = null
}
-fun DrawContext.drawLine(fromX: Int, fromY: Int, toX: Int, toY: Int, color: Color) {
- // TODO: push scissors
- // TODO: use matrix translations and a different render layer
+class LineRenderer(vertexConsumers: MultiBufferSource.BufferSource) :
+ MultiSpecialGuiRenderer<LineRenderState>(vertexConsumers) {
+ override fun getRenderStateClass(): Class<LineRenderState> {
+ return LineRenderState::class.java
+ }
+
+ override fun getTranslateY(height: Int, windowScaleFactor: Int): Float {
+ return height / 2F
+ }
+
+ override fun renderToTexture(
+ state: LineRenderState,
+ matrices: PoseStack
+ ) {
+ val gr = MC.instance.gameRenderer
+ val client = MC.instance
+ gr.globalSettingsUniform
+ .update(
+ state.bounds.width,
+ state.bounds.height,
+ client.options.glintStrength().get(),
+ client.level?.gameTime ?: 0L,
+ client.deltaTracker,
+ client.options.menuBackgroundBlurriness
+ )
+
+ RenderSystem.lineWidth(state.lineWidth)
+ val buf = bufferSource.getBuffer(CustomRenderLayers.LINES)
+ val matrix = matrices.last()
+ val wh = state.w / 2F
+ val hh = state.h / 2F
+ val lowX = -wh
+ val lowY = if (state.direction == LineRenderState.LineDirection.BOTTOM_LEFT_TO_TOP_RIGHT) hh else -hh
+ val highX = wh
+ val highY = -lowY
+ val norm = Vector3f(highX - lowX, highY - lowY, 0F).normalize()
+ buf.addVertex(matrix, lowX, lowY, 0F).setColor(state.color)
+ .setNormal(matrix, norm)
+ buf.addVertex(matrix, highX, highY, 0F).setColor(state.color)
+ .setNormal(matrix, norm)
+ bufferSource.endBatch()
+ gr.globalSettingsUniform
+ .update(
+ client.window.width,
+ client.window.height,
+ client.options.glintStrength().get(),
+ client.level?.gameTime ?: 0L,
+ client.deltaTracker,
+ client.options.menuBackgroundBlurriness
+ )
+
+ }
+
+ override fun getTextureLabel(): String? {
+ return "Firmament Line Renderer"
+ }
+}
+
+
+fun GuiGraphics.drawLine(fromX: Int, fromY: Int, toX: Int, toY: Int, color: Color, lineWidth: Float = 1F) {
if (toY < fromY) {
drawLine(toX, toY, fromX, fromY, color)
return
}
- RenderSystem.lineWidth(MC.window.scaleFactor.toFloat())
- draw { vertexConsumers ->
- val buf = vertexConsumers.getBuffer(RenderInWorldContext.RenderLayers.LINES)
- buf.vertex(fromX.toFloat(), fromY.toFloat(), 0F).color(color.color)
- .normal(toX - fromX.toFloat(), toY - fromY.toFloat(), 0F)
- buf.vertex(toX.toFloat(), toY.toFloat(), 0F).color(color.color)
- .normal(toX - fromX.toFloat(), toY - fromY.toFloat(), 0F)
- }
+ val originalRect = ScreenRectangle(
+ minOf(fromX, toX), minOf(toY, fromY),
+ abs(toX - fromX), abs(toY - fromY)
+ ).transformAxisAligned(pose())
+ val expansionFactor = 3
+ val rect = ScreenRectangle(
+ originalRect.left() - expansionFactor,
+ originalRect.top() - expansionFactor,
+ originalRect.width + expansionFactor * 2,
+ originalRect.height + expansionFactor * 2
+ )
+ // TODO: expand the bounds so that the thickness of the line can be used
+ // TODO: fix this up to work with scissorarea
+ guiRenderState.submitPicturesInPictureState(
+ LineRenderState(
+ rect.left(), rect.right(), rect.top(), rect.bottom(), 1F, rect, lineWidth,
+ originalRect.width, originalRect.height, color.color,
+ if (fromX < toX) LineRenderState.LineDirection.TOP_LEFT_TO_BOTTOM_RIGHT else LineRenderState.LineDirection.BOTTOM_LEFT_TO_TOP_RIGHT
+ )
+ )
}
diff --git a/src/main/kotlin/util/render/DumpTexture.kt b/src/main/kotlin/util/render/DumpTexture.kt
new file mode 100644
index 0000000..2ac6a1c
--- /dev/null
+++ b/src/main/kotlin/util/render/DumpTexture.kt
@@ -0,0 +1,34 @@
+package moe.nea.firmament.util.render
+
+import com.mojang.blaze3d.buffers.GpuBuffer
+import com.mojang.blaze3d.systems.RenderSystem
+import com.mojang.blaze3d.textures.GpuTexture
+import java.io.File
+import com.mojang.blaze3d.platform.NativeImage
+
+fun dumpTexture(gpuTexture: GpuTexture, name: String) {
+ val w = gpuTexture.getWidth(0)
+ val h = gpuTexture.getHeight(0)
+ val buffer = RenderSystem.getDevice()
+ .createBuffer(
+ { "Dump Buffer" },
+ GpuBuffer.USAGE_COPY_DST or GpuBuffer.USAGE_MAP_READ,
+ w * h * gpuTexture.getFormat().pixelSize()
+ )
+ val commandEncoder = RenderSystem.getDevice().createCommandEncoder()
+ commandEncoder.copyTextureToBuffer(
+ gpuTexture, buffer, 0, {
+ val nativeImage = NativeImage(w, h, false)
+ commandEncoder.mapBuffer(buffer, true, false).use { mappedView ->
+ for (i in 0..<w) {
+ for (j in 0..<h) {
+ val color = mappedView.data().getInt((j + i * w) * gpuTexture.format.pixelSize())
+ nativeImage.setPixelABGR(j, h - i - 1, color)
+ }
+ }
+ }
+ buffer.close()
+ nativeImage.writeToFile(File("$name.png"))
+ }, 0
+ )
+}
diff --git a/src/main/kotlin/util/render/FacingThePlayerContext.kt b/src/main/kotlin/util/render/FacingThePlayerContext.kt
index daa8da9..dc45939 100644
--- a/src/main/kotlin/util/render/FacingThePlayerContext.kt
+++ b/src/main/kotlin/util/render/FacingThePlayerContext.kt
@@ -1,21 +1,15 @@
package moe.nea.firmament.util.render
-import com.mojang.blaze3d.systems.RenderSystem
-import io.github.notenoughupdates.moulconfig.platform.next
import org.joml.Matrix4f
-import net.minecraft.client.font.TextRenderer
-import net.minecraft.client.render.BufferRenderer
-import net.minecraft.client.render.GameRenderer
-import net.minecraft.client.render.LightmapTextureManager
-import net.minecraft.client.render.RenderLayer
-import net.minecraft.client.render.Tessellator
-import net.minecraft.client.render.VertexConsumer
-import net.minecraft.client.render.VertexFormat
-import net.minecraft.client.render.VertexFormats
-import net.minecraft.text.Text
-import net.minecraft.util.Identifier
-import net.minecraft.util.math.BlockPos
+import util.render.CustomRenderLayers
+import net.minecraft.client.gui.Font
+import net.minecraft.client.renderer.LightTexture
+import net.minecraft.client.renderer.RenderType
+import com.mojang.blaze3d.vertex.VertexConsumer
+import net.minecraft.network.chat.Component
+import net.minecraft.resources.ResourceLocation
+import net.minecraft.core.BlockPos
import moe.nea.firmament.util.FirmFormatters
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.assertTrueOr
@@ -23,76 +17,76 @@ import moe.nea.firmament.util.assertTrueOr
@RenderContextDSL
class FacingThePlayerContext(val worldContext: RenderInWorldContext) {
val matrixStack by worldContext::matrixStack
- fun waypoint(position: BlockPos, label: Text) {
+ fun waypoint(position: BlockPos, label: Component) {
text(
label,
- Text.literal("§e${FirmFormatters.formatDistance(MC.player?.pos?.distanceTo(position.toCenterPos()) ?: 42069.0)}")
+ Component.literal("§e${FirmFormatters.formatDistance(MC.player?.position?.distanceTo(position.center) ?: 42069.0)}")
)
}
fun text(
- vararg texts: Text,
- verticalAlign: RenderInWorldContext.VerticalAlign = RenderInWorldContext.VerticalAlign.CENTER,
- background: Int = 0x70808080,
+ vararg texts: Component,
+ verticalAlign: RenderInWorldContext.VerticalAlign = RenderInWorldContext.VerticalAlign.CENTER,
+ background: Int = 0x70808080,
) {
assertTrueOr(texts.isNotEmpty()) { return@text }
for ((index, text) in texts.withIndex()) {
- worldContext.matrixStack.push()
- val width = MC.font.getWidth(text)
+ worldContext.matrixStack.pushPose()
+ val width = MC.font.width(text)
worldContext.matrixStack.translate(-width / 2F, verticalAlign.align(index, texts.size), 0F)
val vertexConsumer: VertexConsumer =
- worldContext.vertexConsumers.getBuffer(RenderLayer.getTextBackgroundSeeThrough())
- val matrix4f = worldContext.matrixStack.peek().positionMatrix
- vertexConsumer.vertex(matrix4f, -1.0f, -1.0f, 0.0f).color(background)
- .light(LightmapTextureManager.MAX_BLOCK_LIGHT_COORDINATE).next()
- vertexConsumer.vertex(matrix4f, -1.0f, MC.font.fontHeight.toFloat(), 0.0f).color(background)
- .light(LightmapTextureManager.MAX_BLOCK_LIGHT_COORDINATE).next()
- vertexConsumer.vertex(matrix4f, width.toFloat(), MC.font.fontHeight.toFloat(), 0.0f)
- .color(background)
- .light(LightmapTextureManager.MAX_BLOCK_LIGHT_COORDINATE).next()
- vertexConsumer.vertex(matrix4f, width.toFloat(), -1.0f, 0.0f).color(background)
- .light(LightmapTextureManager.MAX_BLOCK_LIGHT_COORDINATE).next()
+ worldContext.vertexConsumers.getBuffer(RenderType.textBackgroundSeeThrough())
+ val matrix4f = worldContext.matrixStack.last().pose()
+ vertexConsumer.addVertex(matrix4f, -1.0f, -1.0f, 0.0f).setColor(background)
+ .setLight(LightTexture.FULL_BLOCK)
+ vertexConsumer.addVertex(matrix4f, -1.0f, MC.font.lineHeight.toFloat(), 0.0f).setColor(background)
+ .setLight(LightTexture.FULL_BLOCK)
+ vertexConsumer.addVertex(matrix4f, width.toFloat(), MC.font.lineHeight.toFloat(), 0.0f)
+ .setColor(background)
+ .setLight(LightTexture.FULL_BLOCK)
+ vertexConsumer.addVertex(matrix4f, width.toFloat(), -1.0f, 0.0f).setColor(background)
+ .setLight(LightTexture.FULL_BLOCK)
worldContext.matrixStack.translate(0F, 0F, 0.01F)
- MC.font.draw(
+ MC.font.drawInBatch(
text,
0F,
0F,
-1,
false,
- worldContext.matrixStack.peek().positionMatrix,
+ worldContext.matrixStack.last().pose(),
worldContext.vertexConsumers,
- TextRenderer.TextLayerType.SEE_THROUGH,
+ Font.DisplayMode.SEE_THROUGH,
0,
- LightmapTextureManager.MAX_LIGHT_COORDINATE
+ LightTexture.FULL_BRIGHT
)
- worldContext.matrixStack.pop()
+ worldContext.matrixStack.popPose()
}
}
fun texture(
- texture: Identifier, width: Int, height: Int,
- u1: Float, v1: Float,
- u2: Float, v2: Float,
+ texture: ResourceLocation, width: Int, height: Int,
+ u1: Float, v1: Float,
+ u2: Float, v2: Float,
) {
- val buf = worldContext.vertexConsumers.getBuffer(RenderLayer.getGuiTexturedOverlay(texture))
+ val buf = worldContext.vertexConsumers.getBuffer(CustomRenderLayers.GUI_TEXTURED_NO_DEPTH_TRIS.apply(texture)) // TODO: this is strictly an incorrect render layer
val hw = width / 2F
val hh = height / 2F
- val matrix4f: Matrix4f = worldContext.matrixStack.peek().positionMatrix
- buf.vertex(matrix4f, -hw, -hh, 0F)
- .color(-1)
- .texture(u1, v1).next()
- buf.vertex(matrix4f, -hw, +hh, 0F)
- .color(-1)
- .texture(u1, v2).next()
- buf.vertex(matrix4f, +hw, +hh, 0F)
- .color(-1)
- .texture(u2, v2).next()
- buf.vertex(matrix4f, +hw, -hh, 0F)
- .color(-1)
- .texture(u2, v1).next()
- worldContext.vertexConsumers.draw()
+ val matrix4f: Matrix4f = worldContext.matrixStack.last().pose()
+ buf.addVertex(matrix4f, -hw, -hh, 0F)
+ .setColor(-1)
+ .setUv(u1, v1)
+ buf.addVertex(matrix4f, -hw, +hh, 0F)
+ .setColor(-1)
+ .setUv(u1, v2)
+ buf.addVertex(matrix4f, +hw, +hh, 0F)
+ .setColor(-1)
+ .setUv(u2, v2)
+ buf.addVertex(matrix4f, +hw, -hh, 0F)
+ .setColor(-1)
+ .setUv(u2, v1)
+ worldContext.vertexConsumers.endBatch()
}
}
diff --git a/src/main/kotlin/util/render/FirmamentShaders.kt b/src/main/kotlin/util/render/FirmamentShaders.kt
index ba67dbb..53afdf5 100644
--- a/src/main/kotlin/util/render/FirmamentShaders.kt
+++ b/src/main/kotlin/util/render/FirmamentShaders.kt
@@ -1,30 +1,12 @@
package moe.nea.firmament.util.render
-import net.minecraft.client.gl.Defines
-import net.minecraft.client.gl.ShaderProgramKey
-import net.minecraft.client.render.RenderPhase
-import net.minecraft.client.render.VertexFormat
-import net.minecraft.client.render.VertexFormats
-import moe.nea.firmament.Firmament
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.DebugInstantiateEvent
-import moe.nea.firmament.util.MC
object FirmamentShaders {
- val shaders = mutableListOf<ShaderProgramKey>()
-
- private fun shader(name: String, format: VertexFormat, defines: Defines): ShaderProgramKey {
- val key = ShaderProgramKey(Firmament.identifier(name), format, defines)
- shaders.add(key)
- return key
- }
-
- val LINES = RenderPhase.ShaderProgram(shader("core/rendertype_lines", VertexFormats.LINES, Defines.EMPTY))
@Subscribe
fun debugLoad(event: DebugInstantiateEvent) {
- shaders.forEach {
- MC.instance.shaderLoader.getOrCreateProgram(it)
- }
+ // TODO: do i still need to work with shaders like this?
}
}
diff --git a/src/main/kotlin/util/render/LerpUtils.kt b/src/main/kotlin/util/render/LerpUtils.kt
index f2c2f25..e7f226c 100644
--- a/src/main/kotlin/util/render/LerpUtils.kt
+++ b/src/main/kotlin/util/render/LerpUtils.kt
@@ -1,33 +1,40 @@
-
package moe.nea.firmament.util.render
import me.shedaniel.math.Color
+import kotlin.math.absoluteValue
-val pi = Math.PI
-val tau = Math.PI * 2
+val π = Math.PI
+val τ = Math.PI * 2
fun lerpAngle(a: Float, b: Float, progress: Float): Float {
- // TODO: there is at least 10 mods to many in here lol
- val shortestAngle = ((((b.mod(tau) - a.mod(tau)).mod(tau)) + tau + pi).mod(tau)) - pi
- return ((a + (shortestAngle) * progress).mod(tau)).toFloat()
+ // TODO: there is at least 10 mods to many in here lol
+ if (((b - a).absoluteValue - π).absoluteValue < 0.0001) {
+ return lerp(a, b, progress)
+ }
+ val shortestAngle = ((((b.mod(τ) - a.mod(τ)).mod(τ)) + τ + π).mod(τ)) - π
+ return ((a + (shortestAngle) * progress).mod(τ)).toFloat()
}
+fun wrapAngle(angle: Float): Float = (angle.mod(τ) + τ).mod(τ).toFloat()
+fun wrapAngle(angle: Double): Double = (angle.mod(τ) + τ).mod(τ)
+
fun lerp(a: Float, b: Float, progress: Float): Float {
- return a + (b - a) * progress
+ return a + (b - a) * progress
}
+
fun lerp(a: Int, b: Int, progress: Float): Int {
- return (a + (b - a) * progress).toInt()
+ return (a + (b - a) * progress).toInt()
}
fun ilerp(a: Float, b: Float, value: Float): Float {
- return (value - a) / (b - a)
+ return (value - a) / (b - a)
}
fun lerp(a: Color, b: Color, progress: Float): Color {
- return Color.ofRGBA(
- lerp(a.red, b.red, progress),
- lerp(a.green, b.green, progress),
- lerp(a.blue, b.blue, progress),
- lerp(a.alpha, b.alpha, progress),
- )
+ return Color.ofRGBA(
+ lerp(a.red, b.red, progress),
+ lerp(a.green, b.green, progress),
+ lerp(a.blue, b.blue, progress),
+ lerp(a.alpha, b.alpha, progress),
+ )
}
diff --git a/src/main/kotlin/util/render/MultiSpecialGuiRenderState.kt b/src/main/kotlin/util/render/MultiSpecialGuiRenderState.kt
new file mode 100644
index 0000000..a58ffdc
--- /dev/null
+++ b/src/main/kotlin/util/render/MultiSpecialGuiRenderState.kt
@@ -0,0 +1,47 @@
+package moe.nea.firmament.util.render
+
+import net.minecraft.client.gui.navigation.ScreenRectangle
+import net.minecraft.client.gui.render.pip.PictureInPictureRenderer
+import net.minecraft.client.gui.render.state.GuiRenderState
+import net.minecraft.client.gui.render.state.pip.PictureInPictureRenderState
+import net.minecraft.client.renderer.MultiBufferSource
+
+abstract class MultiSpecialGuiRenderState : PictureInPictureRenderState {
+ // I wish i had manifolds @Self type here... Maybe i should switch to java after all :(
+ abstract fun createRenderer(vertexConsumers: MultiBufferSource.BufferSource): MultiSpecialGuiRenderer<out MultiSpecialGuiRenderState>
+ abstract val x1: Int
+ abstract val x2: Int
+ abstract val y1: Int
+ abstract val y2: Int
+ abstract val scale: Float
+ abstract val bounds: ScreenRectangle?
+ abstract val scissorArea: ScreenRectangle?
+ override fun x0(): Int = x1
+
+ override fun x1(): Int = x2
+
+ override fun y0(): Int = y1
+
+ override fun y1(): Int = y2
+
+ override fun scale(): Float = scale
+
+ override fun scissorArea(): ScreenRectangle? = scissorArea
+
+ override fun bounds(): ScreenRectangle? = bounds
+
+}
+
+abstract class MultiSpecialGuiRenderer<T : MultiSpecialGuiRenderState>(
+ vertexConsumers: MultiBufferSource.BufferSource
+) : PictureInPictureRenderer<T>(vertexConsumers) {
+ var wasUsedThisFrame = false
+ fun consumeRender(): Boolean {
+ return wasUsedThisFrame.also { wasUsedThisFrame = false }
+ }
+
+ override fun blitTexture(element: T, state: GuiRenderState) {
+ wasUsedThisFrame = true
+ super.blitTexture(element, state)
+ }
+}
diff --git a/src/main/kotlin/util/render/RenderCircleProgress.kt b/src/main/kotlin/util/render/RenderCircleProgress.kt
index 805633c..acd0210 100644
--- a/src/main/kotlin/util/render/RenderCircleProgress.kt
+++ b/src/main/kotlin/util/render/RenderCircleProgress.kt
@@ -1,93 +1,166 @@
package moe.nea.firmament.util.render
-import com.mojang.blaze3d.systems.RenderSystem
-import io.github.notenoughupdates.moulconfig.platform.next
-import org.joml.Matrix4f
-import org.joml.Vector2f
-import kotlin.math.atan2
-import kotlin.math.tan
-import net.minecraft.client.gui.DrawContext
-import net.minecraft.client.render.BufferRenderer
-import net.minecraft.client.render.RenderLayer
-import net.minecraft.client.render.RenderPhase
-import net.minecraft.client.render.Tessellator
-import net.minecraft.client.render.VertexFormat.DrawMode
-import net.minecraft.client.render.VertexFormats
-import net.minecraft.util.Identifier
+import com.mojang.blaze3d.vertex.VertexFormat
+import util.render.CustomRenderLayers
+import net.minecraft.client.gui.GuiGraphics
+import net.minecraft.client.gui.navigation.ScreenRectangle
+import com.mojang.blaze3d.vertex.BufferBuilder
+import net.minecraft.client.renderer.RenderType
+import net.minecraft.client.renderer.MultiBufferSource
+import com.mojang.blaze3d.vertex.ByteBufferBuilder
+import com.mojang.blaze3d.vertex.PoseStack
+import net.minecraft.resources.ResourceLocation
+import moe.nea.firmament.util.MC
+import moe.nea.firmament.util.collections.nonNegligibleSubSectionsAlignedWith
+import moe.nea.firmament.util.math.Projections
+import moe.nea.firmament.util.mc.CustomRenderPassHelper
object RenderCircleProgress {
- fun renderCircle(
- drawContext: DrawContext,
- texture: Identifier,
- progress: Float,
- u1: Float,
- u2: Float,
- v1: Float,
- v2: Float,
- ) {
- RenderSystem.enableBlend()
- drawContext.draw {
- val bufferBuilder = it.getBuffer(GuiRenderLayers.GUI_TEXTURED_TRIS.apply(texture))
- val matrix: Matrix4f = drawContext.matrices.peek().positionMatrix
-
- val corners = listOf(
- Vector2f(0F, -1F),
- Vector2f(1F, -1F),
- Vector2f(1F, 0F),
- Vector2f(1F, 1F),
- Vector2f(0F, 1F),
- Vector2f(-1F, 1F),
- Vector2f(-1F, 0F),
- Vector2f(-1F, -1F),
- )
- for (i in (0 until 8)) {
- if (progress < i / 8F) {
- break
+ data class State(
+ override val x1: Int,
+ override val x2: Int,
+ override val y1: Int,
+ override val y2: Int,
+ val layer: RenderType.CompositeRenderType,
+ val u1: Float,
+ val u2: Float,
+ val v1: Float,
+ val v2: Float,
+ val angleRadians: ClosedFloatingPointRange<Float>,
+ val color: Int,
+ val innerCutoutRadius: Float,
+ override val scale: Float,
+ override val bounds: ScreenRectangle?,
+ override val scissorArea: ScreenRectangle?,
+ ) : MultiSpecialGuiRenderState() {
+ override fun createRenderer(vertexConsumers: MultiBufferSource.BufferSource): MultiSpecialGuiRenderer<out MultiSpecialGuiRenderState> {
+ return Renderer(vertexConsumers)
+ }
+ }
+
+ class Renderer(vertexConsumers: MultiBufferSource.BufferSource) :
+ MultiSpecialGuiRenderer<State>(vertexConsumers) {
+ override fun renderToTexture(
+ state: State,
+ matrices: PoseStack
+ ) {
+ matrices.pushPose()
+ matrices.translate(0F, -1F, 0F)
+ val sections = state.angleRadians.nonNegligibleSubSectionsAlignedWith((τ / 8f).toFloat())
+ .zipWithNext().toList()
+ val u1 = state.u1
+ val u2 = state.u2
+ val v1 = state.v1
+ val v2 = state.v2
+ val color = state.color
+ val matrix = matrices.last().pose()
+ ByteBufferBuilder(state.layer.format().vertexSize * sections.size * 3).use { allocator ->
+
+ val bufferBuilder = BufferBuilder(allocator, VertexFormat.Mode.TRIANGLES, state.layer.format())
+
+ for ((sectionStart, sectionEnd) in sections) {
+ val firstPoint = Projections.Two.projectAngleOntoUnitBox(sectionStart.toDouble())
+ val secondPoint = Projections.Two.projectAngleOntoUnitBox(sectionEnd.toDouble())
+ fun ilerp(f: Float): Float =
+ ilerp(-1f, 1f, f)
+
+ bufferBuilder
+ .addVertex(matrix, secondPoint.x, secondPoint.y, 0F)
+ .setUv(lerp(u1, u2, ilerp(secondPoint.x)), lerp(v1, v2, ilerp(secondPoint.y)))
+ .setColor(color)
+
+ bufferBuilder
+ .addVertex(matrix, firstPoint.x, firstPoint.y, 0F)
+ .setUv(lerp(u1, u2, ilerp(firstPoint.x)), lerp(v1, v2, ilerp(firstPoint.y)))
+ .setColor(color)
+
+ bufferBuilder
+ .addVertex(matrix, 0F, 0F, 0F)
+ .setUv(lerp(u1, u2, ilerp(0F)), lerp(v1, v2, ilerp(0F)))
+ .setColor(color)
+
}
- val second = corners[(i + 1) % 8]
- val first = corners[i]
- if (progress <= (i + 1) / 8F) {
- val internalProgress = 1 - (progress - i / 8F) * 8F
- val angle = lerpAngle(
- atan2(second.y, second.x),
- atan2(first.y, first.x),
- internalProgress
- )
- if (angle < tau / 8 || angle >= tau * 7 / 8) {
- second.set(1F, tan(angle))
- } else if (angle < tau * 3 / 8) {
- second.set(1 / tan(angle), 1F)
- } else if (angle < tau * 5 / 8) {
- second.set(-1F, -tan(angle))
- } else {
- second.set(-1 / tan(angle), -1F)
+
+ bufferBuilder.buildOrThrow().use { buffer ->
+ if (state.innerCutoutRadius <= 0) {
+ state.layer.draw(buffer)
+ return
+ }
+ CustomRenderPassHelper(
+ { "RenderCircleProgress" },
+ VertexFormat.Mode.TRIANGLES,
+ state.layer.format(),
+ MC.instance.mainRenderTarget,
+ false,
+ ).use { renderPass ->
+ renderPass.uploadVertices(buffer)
+ renderPass.setAllDefaultUniforms()
+ renderPass.setPipeline(state.layer.renderPipeline)
+ renderPass.setUniform("CutoutRadius", 4) {
+ it.putFloat(state.innerCutoutRadius)
+ }
+ renderPass.draw()
}
}
-
- fun ilerp(f: Float): Float =
- ilerp(-1f, 1f, f)
-
- bufferBuilder
- .vertex(matrix, second.x, second.y, 0F)
- .texture(lerp(u1, u2, ilerp(second.x)), lerp(v1, v2, ilerp(second.y)))
- .color(-1)
- .next()
- bufferBuilder
- .vertex(matrix, first.x, first.y, 0F)
- .texture(lerp(u1, u2, ilerp(first.x)), lerp(v1, v2, ilerp(first.y)))
- .color(-1)
- .next()
- bufferBuilder
- .vertex(matrix, 0F, 0F, 0F)
- .texture(lerp(u1, u2, ilerp(0F)), lerp(v1, v2, ilerp(0F)))
- .color(-1)
- .next()
}
+ matrices.popPose()
+ }
+
+ override fun getRenderStateClass(): Class<State> {
+ return State::class.java
+ }
+
+ override fun getTextureLabel(): String {
+ return "Firmament Circle"
}
- RenderSystem.disableBlend()
}
+ fun renderCircularSlice(
+ drawContext: GuiGraphics,
+ layer: RenderType.CompositeRenderType,
+ u1: Float,
+ u2: Float,
+ v1: Float,
+ v2: Float,
+ angleRadians: ClosedFloatingPointRange<Float>,
+ color: Int = -1,
+ innerCutoutRadius: Float = 0F
+ ) {
+ val screenRect = ScreenRectangle(-1, -1, 2, 2).transformAxisAligned(drawContext.pose())
+ drawContext.guiRenderState.submitPicturesInPictureState(
+ State(
+ screenRect.left(), screenRect.right(),
+ screenRect.top(), screenRect.bottom(),
+ layer,
+ u1, u2, v1, v2,
+ angleRadians,
+ color,
+ innerCutoutRadius,
+ screenRect.width / 2F,
+ screenRect,
+ null
+ )
+ )
+ }
+ fun renderCircle(
+ drawContext: GuiGraphics,
+ texture: ResourceLocation,
+ progress: Float,
+ u1: Float,
+ u2: Float,
+ v1: Float,
+ v2: Float,
+ color: Int = -1
+ ) {
+ renderCircularSlice(
+ drawContext,
+ CustomRenderLayers.GUI_TEXTURED_NO_DEPTH_TRIS.apply(texture),
+ u1, u2, v1, v2,
+ (-τ / 4).toFloat()..(progress * τ - τ / 4).toFloat(),
+ color = color
+ )
+ }
}
diff --git a/src/main/kotlin/util/render/RenderInWorldContext.kt b/src/main/kotlin/util/render/RenderInWorldContext.kt
index bb58200..f6877c8 100644
--- a/src/main/kotlin/util/render/RenderInWorldContext.kt
+++ b/src/main/kotlin/util/render/RenderInWorldContext.kt
@@ -1,89 +1,80 @@
package moe.nea.firmament.util.render
import com.mojang.blaze3d.systems.RenderSystem
-import io.github.notenoughupdates.moulconfig.platform.next
-import java.lang.Math.pow
import org.joml.Matrix4f
import org.joml.Vector3f
-import net.minecraft.client.gl.VertexBuffer
-import net.minecraft.client.render.Camera
-import net.minecraft.client.render.RenderLayer
-import net.minecraft.client.render.RenderPhase
-import net.minecraft.client.render.RenderTickCounter
-import net.minecraft.client.render.Tessellator
-import net.minecraft.client.render.VertexConsumer
-import net.minecraft.client.render.VertexConsumerProvider
-import net.minecraft.client.render.VertexFormat
-import net.minecraft.client.render.VertexFormats
-import net.minecraft.client.texture.Sprite
-import net.minecraft.client.util.math.MatrixStack
-import net.minecraft.text.Text
-import net.minecraft.util.Identifier
-import net.minecraft.util.math.BlockPos
-import net.minecraft.util.math.Vec3d
+import util.render.CustomRenderLayers
+import kotlin.math.pow
+import net.minecraft.client.Camera
+import net.minecraft.client.renderer.RenderType
+import net.minecraft.client.renderer.ItemBlockRenderTypes
+import net.minecraft.client.DeltaTracker
+import net.minecraft.client.renderer.Sheets
+import com.mojang.blaze3d.vertex.VertexConsumer
+import net.minecraft.client.renderer.MultiBufferSource
+import net.minecraft.client.renderer.state.CameraRenderState
+import net.minecraft.client.renderer.texture.TextureAtlasSprite
+import com.mojang.blaze3d.vertex.PoseStack
+import net.minecraft.network.chat.Component
+import net.minecraft.resources.ResourceLocation
+import net.minecraft.core.BlockPos
+import net.minecraft.world.phys.AABB
+import net.minecraft.world.phys.Vec3
import moe.nea.firmament.events.WorldRenderLastEvent
import moe.nea.firmament.util.FirmFormatters
import moe.nea.firmament.util.MC
@RenderContextDSL
class RenderInWorldContext private constructor(
- private val tesselator: Tessellator,
- val matrixStack: MatrixStack,
- private val camera: Camera,
- private val tickCounter: RenderTickCounter,
- val vertexConsumers: VertexConsumerProvider.Immediate,
+ val matrixStack: PoseStack,
+ private val camera: CameraRenderState,
+ val vertexConsumers: MultiBufferSource.BufferSource,
) {
-
- object RenderLayers {
- val TRANSLUCENT_TRIS = RenderLayer.of("firmament_translucent_tris",
- VertexFormats.POSITION_COLOR,
- VertexFormat.DrawMode.TRIANGLES,
- RenderLayer.CUTOUT_BUFFER_SIZE,
- false, true,
- RenderLayer.MultiPhaseParameters.builder()
- .depthTest(RenderPhase.ALWAYS_DEPTH_TEST)
- .transparency(RenderPhase.TRANSLUCENT_TRANSPARENCY)
- .program(RenderPhase.POSITION_COLOR_PROGRAM)
- .build(false))
- val LINES = RenderLayer.of("firmament_rendertype_lines",
- VertexFormats.LINES,
- VertexFormat.DrawMode.LINES,
- RenderLayer.CUTOUT_BUFFER_SIZE,
- false, false, // do we need translucent? i dont think so
- RenderLayer.MultiPhaseParameters.builder()
- .depthTest(RenderPhase.ALWAYS_DEPTH_TEST)
- .program(FirmamentShaders.LINES)
- .build(false)
- )
- val COLORED_QUADS = RenderLayer.of(
- "firmament_quads",
- VertexFormats.POSITION_COLOR,
- VertexFormat.DrawMode.QUADS,
- RenderLayer.CUTOUT_BUFFER_SIZE,
- false, true,
- RenderLayer.MultiPhaseParameters.builder()
- .depthTest(RenderPhase.ALWAYS_DEPTH_TEST)
- .program(RenderPhase.POSITION_COLOR_PROGRAM)
- .transparency(RenderPhase.TRANSLUCENT_TRANSPARENCY)
- .build(false)
- )
- }
-
- @Deprecated("stateful color management is no longer a thing")
- fun color(color: me.shedaniel.math.Color) {
- color(color.red / 255F, color.green / 255f, color.blue / 255f, color.alpha / 255f)
+ fun block(blockPos: BlockPos, color: Int) {
+ matrixStack.pushPose()
+ matrixStack.translate(blockPos.x.toFloat(), blockPos.y.toFloat(), blockPos.z.toFloat())
+ buildCube(matrixStack.last().pose(), vertexConsumers.getBuffer(CustomRenderLayers.COLORED_QUADS), color)
+ matrixStack.popPose()
}
- @Deprecated("stateful color management is no longer a thing")
- fun color(red: Float, green: Float, blue: Float, alpha: Float) {
- RenderSystem.setShaderColor(red, green, blue, alpha)
+ fun box(aabb: AABB, color: Int) {
+ matrixStack.pushPose()
+ matrixStack.translate(aabb.minX, aabb.minY, aabb.minZ)
+ matrixStack.scale(aabb.xsize.toFloat(), aabb.ysize.toFloat(), aabb.zsize.toFloat())
+ buildCube(matrixStack.last().pose(), vertexConsumers.getBuffer(CustomRenderLayers.COLORED_QUADS), color)
+ matrixStack.popPose()
}
- fun block(blockPos: BlockPos, color: Int) {
- matrixStack.push()
- matrixStack.translate(blockPos.x.toFloat(), blockPos.y.toFloat(), blockPos.z.toFloat())
- buildCube(matrixStack.peek().positionMatrix, vertexConsumers.getBuffer(RenderLayers.COLORED_QUADS), color)
- matrixStack.pop()
+ fun sharedVoxelSurface(blocks: Set<BlockPos>, color: Int) {
+ val m = BlockPos.MutableBlockPos()
+ val l = vertexConsumers.getBuffer(CustomRenderLayers.COLORED_QUADS)
+ blocks.forEach {
+ matrixStack.pushPose()
+ matrixStack.translate(it.x.toFloat(), it.y.toFloat(), it.z.toFloat())
+ val p = matrixStack.last().pose()
+ m.set(it)
+ if (m.setX(it.x + 1) !in blocks) {
+ buildFaceXP(p, l, color)
+ }
+ if (m.setX(it.x - 1) !in blocks) {
+ buildFaceXN(p, l, color)
+ }
+ m.set(it)
+ if (m.setY(it.y + 1) !in blocks) {
+ buildFaceYP(p, l, color)
+ }
+ if (m.setY(it.y - 1) !in blocks) {
+ buildFaceYN(p, l, color)
+ }
+ m.set(it)
+ if (m.setZ(it.z + 1) !in blocks) {
+ buildFaceZP(p, l, color)
+ }
+ if (m.setZ(it.z - 1) !in blocks) {
+ buildFaceZN(p, l, color)
+ }
+ matrixStack.popPose()
+ }
}
enum class VerticalAlign {
@@ -91,46 +82,46 @@ class RenderInWorldContext private constructor(
fun align(index: Int, count: Int): Float {
return when (this) {
- CENTER -> (index - count / 2F) * (1 + MC.font.fontHeight.toFloat())
- BOTTOM -> (index - count) * (1 + MC.font.fontHeight.toFloat())
- TOP -> (index) * (1 + MC.font.fontHeight.toFloat())
+ CENTER -> (index - count / 2F) * (1 + MC.font.lineHeight.toFloat())
+ BOTTOM -> (index - count) * (1 + MC.font.lineHeight.toFloat())
+ TOP -> (index) * (1 + MC.font.lineHeight.toFloat())
}
}
}
- fun waypoint(position: BlockPos, vararg label: Text) {
+ fun waypoint(position: BlockPos, vararg label: Component) {
text(
- position.toCenterPos(),
+ position.center,
*label,
- Text.literal("§e${FirmFormatters.formatDistance(MC.player?.pos?.distanceTo(position.toCenterPos()) ?: 42069.0)}"),
+ Component.literal("§e${FirmFormatters.formatDistance(MC.player?.position?.distanceTo(position.center) ?: 42069.0)}"),
background = 0xAA202020.toInt()
)
}
- fun withFacingThePlayer(position: Vec3d, block: FacingThePlayerContext.() -> Unit) {
- matrixStack.push()
+ fun withFacingThePlayer(position: Vec3, block: FacingThePlayerContext.() -> Unit) {
+ matrixStack.pushPose()
matrixStack.translate(position.x, position.y, position.z)
val actualCameraDistance = position.distanceTo(camera.pos)
val distanceToMoveTowardsCamera = if (actualCameraDistance < 10) 0.0 else -(actualCameraDistance - 10.0)
- val vec = position.subtract(camera.pos).multiply(distanceToMoveTowardsCamera / actualCameraDistance)
+ val vec = position.subtract(camera.pos).scale(distanceToMoveTowardsCamera / actualCameraDistance)
matrixStack.translate(vec.x, vec.y, vec.z)
- matrixStack.multiply(camera.rotation)
+ matrixStack.mulPose(camera.orientation)
matrixStack.scale(0.025F, -0.025F, 1F)
FacingThePlayerContext(this).run(block)
- matrixStack.pop()
- vertexConsumers.drawCurrentLayer()
+ matrixStack.popPose()
+ vertexConsumers.endLastBatch()
}
- fun sprite(position: Vec3d, sprite: Sprite, width: Int, height: Int) {
+ fun sprite(position: Vec3, sprite: TextureAtlasSprite, width: Int, height: Int) {
texture(
- position, sprite.atlasId, width, height, sprite.minU, sprite.minV, sprite.maxU, sprite.maxV
+ position, sprite.atlasLocation(), width, height, sprite.u0, sprite.v0, sprite.u1, sprite.v1
)
}
fun texture(
- position: Vec3d, texture: Identifier, width: Int, height: Int,
+ position: Vec3, texture: ResourceLocation, width: Int, height: Int,
u1: Float, v1: Float,
u2: Float, v2: Float,
) {
@@ -140,8 +131,8 @@ class RenderInWorldContext private constructor(
}
fun text(
- position: Vec3d,
- vararg texts: Text,
+ position: Vec3,
+ vararg texts: Component,
verticalAlign: VerticalAlign = VerticalAlign.CENTER,
background: Int = 0x70808080
) {
@@ -150,42 +141,50 @@ class RenderInWorldContext private constructor(
}
}
- fun tinyBlock(vec3d: Vec3d, size: Float, color: Int) {
- matrixStack.push()
+ fun tinyBlock(vec3d: Vec3, size: Float, color: Int) {
+ matrixStack.pushPose()
matrixStack.translate(vec3d.x, vec3d.y, vec3d.z)
matrixStack.scale(size, size, size)
matrixStack.translate(-.5, -.5, -.5)
- buildCube(matrixStack.peek().positionMatrix, vertexConsumers.getBuffer(RenderLayers.COLORED_QUADS), color)
- matrixStack.pop()
- vertexConsumers.draw()
+ buildCube(matrixStack.last().pose(), vertexConsumers.getBuffer(CustomRenderLayers.COLORED_QUADS), color)
+ matrixStack.popPose()
+ vertexConsumers.endBatch()
}
fun wireframeCube(blockPos: BlockPos, lineWidth: Float = 10F) {
- val buf = vertexConsumers.getBuffer(RenderLayer.LINES)
- matrixStack.push()
+ val buf = vertexConsumers.getBuffer(RenderType.LINES)
+ matrixStack.pushPose()
+ // TODO: add color arg to this
// TODO: this does not render through blocks (or water layers) anymore
- RenderSystem.lineWidth(lineWidth / pow(camera.pos.squaredDistanceTo(blockPos.toCenterPos()), 0.25).toFloat())
- matrixStack.translate(blockPos.x.toFloat(), blockPos.y.toFloat(), blockPos.z.toFloat())
- buildWireFrameCube(matrixStack.peek(), buf)
- matrixStack.pop()
- vertexConsumers.draw()
+ RenderSystem.lineWidth(lineWidth / camera.pos.distanceToSqr(blockPos.center).pow(0.25).toFloat())
+ val offset = 1 / 512F
+ matrixStack.translate(
+ blockPos.x.toFloat() - offset,
+ blockPos.y.toFloat() - offset,
+ blockPos.z.toFloat() - offset
+ )
+ val scale = 1 + 2 * offset
+ matrixStack.scale(scale, scale, scale)
+
+ buildWireFrameCube(matrixStack.last(), buf)
+ matrixStack.popPose()
+ vertexConsumers.endBatch()
}
- fun line(vararg points: Vec3d, lineWidth: Float = 10F) {
- line(points.toList(), lineWidth)
+ fun line(vararg points: Vec3, color: Int, lineWidth: Float = 10F) {
+ line(points.toList(), color, lineWidth)
}
- fun tracer(toWhere: Vec3d, lineWidth: Float = 3f) {
- val cameraForward = Vector3f(0f, 0f, -1f).rotate(camera.rotation)
- line(camera.pos.add(Vec3d(cameraForward)), toWhere, lineWidth = lineWidth)
+ fun tracer(toWhere: Vec3, color: Int, lineWidth: Float = 3f) {
+ val cameraForward = Vector3f(0f, 0f, -1f).rotate(camera.orientation)
+ line(camera.pos.add(Vec3(cameraForward)), toWhere, color = color, lineWidth = lineWidth)
}
- fun line(points: List<Vec3d>, lineWidth: Float = 10F) {
+ fun line(points: List<Vec3>, color: Int, lineWidth: Float = 10F) {
RenderSystem.lineWidth(lineWidth)
- // TODO: replace with renderlayers
- val buffer = tesselator.begin(VertexFormat.DrawMode.LINES, VertexFormats.LINES)
+ val buffer = vertexConsumers.getBuffer(CustomRenderLayers.LINES)
- val matrix = matrixStack.peek()
+ val matrix = matrixStack.last()
var lastNormal: Vector3f? = null
points.zipWithNext().forEach { (a, b) ->
val normal = Vector3f(b.x.toFloat(), b.y.toFloat(), b.z.toFloat())
@@ -193,23 +192,22 @@ class RenderInWorldContext private constructor(
.normalize()
val lastNormal0 = lastNormal ?: normal
lastNormal = normal
- buffer.vertex(matrix.positionMatrix, a.x.toFloat(), a.y.toFloat(), a.z.toFloat())
- .color(-1)
- .normal(matrix, lastNormal0.x, lastNormal0.y, lastNormal0.z)
- .next()
- buffer.vertex(matrix.positionMatrix, b.x.toFloat(), b.y.toFloat(), b.z.toFloat())
- .color(-1)
- .normal(matrix, normal.x, normal.y, normal.z)
- .next()
+ buffer.addVertex(matrix.pose(), a.x.toFloat(), a.y.toFloat(), a.z.toFloat())
+ .setColor(color)
+ .setNormal(matrix, lastNormal0.x, lastNormal0.y, lastNormal0.z)
+
+ buffer.addVertex(matrix.pose(), b.x.toFloat(), b.y.toFloat(), b.z.toFloat())
+ .setColor(color)
+ .setNormal(matrix, normal.x, normal.y, normal.z)
+
}
- RenderLayers.LINES.draw(buffer.end())
}
// TODO: put the favourite icons in front of items again
companion object {
private fun doLine(
- matrix: MatrixStack.Entry,
+ matrix: PoseStack.Pose,
buf: VertexConsumer,
i: Float,
j: Float,
@@ -221,18 +219,18 @@ class RenderInWorldContext private constructor(
val normal = Vector3f(x, y, z)
.sub(i, j, k)
.normalize()
- buf.vertex(matrix.positionMatrix, i, j, k)
- .normal(matrix, normal.x, normal.y, normal.z)
- .color(-1)
- .next()
- buf.vertex(matrix.positionMatrix, x, y, z)
- .normal(matrix, normal.x, normal.y, normal.z)
- .color(-1)
- .next()
+ buf.addVertex(matrix.pose(), i, j, k)
+ .setNormal(matrix, normal.x, normal.y, normal.z)
+ .setColor(-1)
+
+ buf.addVertex(matrix.pose(), x, y, z)
+ .setNormal(matrix, normal.x, normal.y, normal.z)
+ .setColor(-1)
+
}
- private fun buildWireFrameCube(matrix: MatrixStack.Entry, buf: VertexConsumer) {
+ private fun buildWireFrameCube(matrix: PoseStack.Pose, buf: VertexConsumer) {
for (i in 0..1) {
for (j in 0..1) {
val i = i.toFloat()
@@ -244,68 +242,72 @@ class RenderInWorldContext private constructor(
}
}
- private fun buildCube(matrix: Matrix4f, buf: VertexConsumer, color: Int) {
- // Y-
- buf.vertex(matrix, 0F, 0F, 0F).color(color)
- buf.vertex(matrix, 0F, 0F, 1F).color(color)
- buf.vertex(matrix, 1F, 0F, 1F).color(color)
- buf.vertex(matrix, 1F, 0F, 0F).color(color)
- // Y+
- buf.vertex(matrix, 0F, 1F, 0F).color(color)
- buf.vertex(matrix, 1F, 1F, 0F).color(color)
- buf.vertex(matrix, 1F, 1F, 1F).color(color)
- buf.vertex(matrix, 0F, 1F, 1F).color(color)
- // X-
- buf.vertex(matrix, 0F, 0F, 0F).color(color)
- buf.vertex(matrix, 0F, 0F, 1F).color(color)
- buf.vertex(matrix, 0F, 1F, 1F).color(color)
- buf.vertex(matrix, 0F, 1F, 0F).color(color)
- // X+
- buf.vertex(matrix, 1F, 0F, 0F).color(color)
- buf.vertex(matrix, 1F, 1F, 0F).color(color)
- buf.vertex(matrix, 1F, 1F, 1F).color(color)
- buf.vertex(matrix, 1F, 0F, 1F).color(color)
- // Z-
- buf.vertex(matrix, 0F, 0F, 0F).color(color)
- buf.vertex(matrix, 1F, 0F, 0F).color(color)
- buf.vertex(matrix, 1F, 1F, 0F).color(color)
- buf.vertex(matrix, 0F, 1F, 0F).color(color)
- // Z+
- buf.vertex(matrix, 0F, 0F, 1F).color(color)
- buf.vertex(matrix, 0F, 1F, 1F).color(color)
- buf.vertex(matrix, 1F, 1F, 1F).color(color)
- buf.vertex(matrix, 1F, 0F, 1F).color(color)
+ private fun buildFaceZP(matrix: Matrix4f, buf: VertexConsumer, rgba: Int) {
+ buf.addVertex(matrix, 0F, 0F, 1F).setColor(rgba)
+ buf.addVertex(matrix, 0F, 1F, 1F).setColor(rgba)
+ buf.addVertex(matrix, 1F, 1F, 1F).setColor(rgba)
+ buf.addVertex(matrix, 1F, 0F, 1F).setColor(rgba)
}
+ private fun buildFaceZN(matrix: Matrix4f, buf: VertexConsumer, rgba: Int) {
+ buf.addVertex(matrix, 0F, 0F, 0F).setColor(rgba)
+ buf.addVertex(matrix, 1F, 0F, 0F).setColor(rgba)
+ buf.addVertex(matrix, 1F, 1F, 0F).setColor(rgba)
+ buf.addVertex(matrix, 0F, 1F, 0F).setColor(rgba)
+ }
+
+ private fun buildFaceXP(matrix: Matrix4f, buf: VertexConsumer, rgba: Int) {
+ buf.addVertex(matrix, 1F, 0F, 0F).setColor(rgba)
+ buf.addVertex(matrix, 1F, 1F, 0F).setColor(rgba)
+ buf.addVertex(matrix, 1F, 1F, 1F).setColor(rgba)
+ buf.addVertex(matrix, 1F, 0F, 1F).setColor(rgba)
+ }
+
+ private fun buildFaceXN(matrix: Matrix4f, buf: VertexConsumer, rgba: Int) {
+ buf.addVertex(matrix, 0F, 0F, 0F).setColor(rgba)
+ buf.addVertex(matrix, 0F, 0F, 1F).setColor(rgba)
+ buf.addVertex(matrix, 0F, 1F, 1F).setColor(rgba)
+ buf.addVertex(matrix, 0F, 1F, 0F).setColor(rgba)
+ }
+
+ private fun buildFaceYN(matrix: Matrix4f, buf: VertexConsumer, rgba: Int) {
+ buf.addVertex(matrix, 0F, 0F, 0F).setColor(rgba)
+ buf.addVertex(matrix, 0F, 0F, 1F).setColor(rgba)
+ buf.addVertex(matrix, 1F, 0F, 1F).setColor(rgba)
+ buf.addVertex(matrix, 1F, 0F, 0F).setColor(rgba)
+ }
+
+ private fun buildFaceYP(matrix: Matrix4f, buf: VertexConsumer, rgba: Int) {
+ buf.addVertex(matrix, 0F, 1F, 0F).setColor(rgba)
+ buf.addVertex(matrix, 1F, 1F, 0F).setColor(rgba)
+ buf.addVertex(matrix, 1F, 1F, 1F).setColor(rgba)
+ buf.addVertex(matrix, 0F, 1F, 1F).setColor(rgba)
+ }
+
+ private fun buildCube(matrix4f: Matrix4f, buf: VertexConsumer, rgba: Int) {
+ buildFaceXP(matrix4f, buf, rgba)
+ buildFaceXN(matrix4f, buf, rgba)
+ buildFaceYP(matrix4f, buf, rgba)
+ buildFaceYN(matrix4f, buf, rgba)
+ buildFaceZP(matrix4f, buf, rgba)
+ buildFaceZN(matrix4f, buf, rgba)
+ }
fun renderInWorld(event: WorldRenderLastEvent, block: RenderInWorldContext. () -> Unit) {
- // TODO: there should be *no more global state*. the only thing we should be doing is render layers. that includes settings like culling, blending, shader color, and depth testing
- // For now i will let these functions remain, but this needs to go before i do a full (non-beta) release
- RenderSystem.disableDepthTest()
- RenderSystem.enableBlend()
- RenderSystem.defaultBlendFunc()
- RenderSystem.disableCull()
-
- event.matrices.push()
+
+ event.matrices.pushPose()
event.matrices.translate(-event.camera.pos.x, -event.camera.pos.y, -event.camera.pos.z)
val ctx = RenderInWorldContext(
- RenderSystem.renderThreadTesselator(),
event.matrices,
event.camera,
- event.tickCounter,
event.vertexConsumers
)
block(ctx)
- event.matrices.pop()
- event.vertexConsumers.draw()
- RenderSystem.setShaderColor(1F, 1F, 1F, 1F)
- VertexBuffer.unbind()
- RenderSystem.enableDepthTest()
- RenderSystem.enableCull()
- RenderSystem.disableBlend()
+ event.matrices.popPose()
+ event.vertexConsumers.endBatch()
}
}
}
diff --git a/src/main/kotlin/util/render/TintedOverlayTexture.kt b/src/main/kotlin/util/render/TintedOverlayTexture.kt
index a02eccc..6513574 100644
--- a/src/main/kotlin/util/render/TintedOverlayTexture.kt
+++ b/src/main/kotlin/util/render/TintedOverlayTexture.kt
@@ -1,10 +1,8 @@
package moe.nea.firmament.util.render
-import com.mojang.blaze3d.platform.GlConst
-import com.mojang.blaze3d.systems.RenderSystem
import me.shedaniel.math.Color
-import net.minecraft.client.render.OverlayTexture
-import net.minecraft.util.math.ColorHelper
+import net.minecraft.client.renderer.texture.OverlayTexture
+import net.minecraft.util.ARGB
import moe.nea.firmament.util.ErrorUtil
class TintedOverlayTexture : OverlayTexture() {
@@ -14,31 +12,24 @@ class TintedOverlayTexture : OverlayTexture() {
private var lastColor: Color? = null
fun setColor(color: Color): TintedOverlayTexture {
- val image = ErrorUtil.notNullOr(texture.image, "Disposed TintedOverlayTexture written to") { return this }
+ val image = ErrorUtil.notNullOr(texture.pixels, "Disposed TintedOverlayTexture written to") { return this }
if (color == lastColor) return this
lastColor = color
for (i in 0..<size) {
for (j in 0..<size) {
if (i < 8) {
- image.setColorArgb(j, i, 0xB2FF0000.toInt())
+ image.setPixel(j, i, 0xB2FF0000.toInt())
} else {
val k = ((1F - j / 15F * 0.75F) * 255F).toInt()
- image.setColorArgb(j, i, ColorHelper.withAlpha(k, color.color))
+ image.setPixel(j, i, ARGB.color(k, color.color))
}
}
}
- RenderSystem.activeTexture(GlConst.GL_TEXTURE1)
- texture.bindTexture()
texture.setFilter(false, false)
texture.setClamp(true)
- image.upload(0,
- 0, 0,
- 0, 0,
- image.width, image.height,
- false)
- RenderSystem.activeTexture(GlConst.GL_TEXTURE0)
+ texture.upload()
return this
}
}
diff --git a/src/main/kotlin/util/render/TranslatedScissors.kt b/src/main/kotlin/util/render/TranslatedScissors.kt
index 8f8bdcf..e337cf0 100644
--- a/src/main/kotlin/util/render/TranslatedScissors.kt
+++ b/src/main/kotlin/util/render/TranslatedScissors.kt
@@ -1,26 +1,31 @@
-
package moe.nea.firmament.util.render
-import org.joml.Matrix4f
-import org.joml.Vector4f
-import net.minecraft.client.gui.DrawContext
+import me.shedaniel.math.Rectangle
+import org.joml.Matrix3x2f
+import org.joml.Vector3f
+import net.minecraft.client.gui.GuiGraphics
+
+fun GuiGraphics.enableScissorWithTranslation(rect: Rectangle) {
+ enableScissor(rect.minX, rect.minY, rect.maxX, rect.maxY)
+}
-fun DrawContext.enableScissorWithTranslation(x1: Float, y1: Float, x2: Float, y2: Float) {
- enableScissor(x1.toInt(), y1.toInt(), x2.toInt(), y2.toInt())
+fun GuiGraphics.enableScissorWithTranslation(x1: Float, y1: Float, x2: Float, y2: Float) {
+ 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)
- target.mul(pMat)
- val scissorX1 = target.x
- val scissorY1 = target.y
+fun GuiGraphics.enableScissorWithoutTranslation(x1: Float, y1: Float, x2: Float, y2: Float) {
+ val pMat = Matrix3x2f(pose()).invert()
+ var target = Vector3f()
+
+ target.set(x1, y1, 1F)
+ target.mul(pMat)
+ val scissorX1 = target.x
+ val scissorY1 = target.y
- target.set(x2, y2, 0f, 1f)
- target.mul(pMat)
- val scissorX2 = target.x
- val scissorY2 = target.y
+ target.set(x2, y2, 1F)
+ target.mul(pMat)
+ val scissorX2 = target.x
+ val scissorY2 = target.y
- enableScissor(scissorX1.toInt(), scissorY1.toInt(), scissorX2.toInt(), scissorY2.toInt())
+ enableScissor(scissorX1.toInt(), scissorY1.toInt(), scissorX2.toInt(), scissorY2.toInt())
}
diff --git a/src/main/kotlin/util/skyblock/AbilityUtils.kt b/src/main/kotlin/util/skyblock/AbilityUtils.kt
index 0d7d2b7..9ba182d 100644
--- a/src/main/kotlin/util/skyblock/AbilityUtils.kt
+++ b/src/main/kotlin/util/skyblock/AbilityUtils.kt
@@ -1,8 +1,8 @@
package moe.nea.firmament.util.skyblock
import kotlin.time.Duration
-import net.minecraft.item.ItemStack
-import net.minecraft.text.Text
+import net.minecraft.world.item.ItemStack
+import net.minecraft.network.chat.Component
import moe.nea.firmament.util.ErrorUtil
import moe.nea.firmament.util.directLiteralStringContent
import moe.nea.firmament.util.mc.loreAccordingToNbt
@@ -17,7 +17,7 @@ object AbilityUtils {
val hasPowerScroll: Boolean,
val activation: AbilityActivation,
val manaCost: Int?,
- val descriptionLines: List<Text>,
+ val descriptionLines: List<Component>,
val cooldown: Duration?,
)
@@ -40,7 +40,7 @@ object AbilityUtils {
}
private val abilityNameRegex = "Ability: (?<name>.*?) *".toPattern()
- private fun findAbility(iterator: ListIterator<Text>): ItemAbility? {
+ private fun findAbility(iterator: ListIterator<Component>): ItemAbility? {
if (!iterator.hasNext()) {
return null
}
@@ -72,7 +72,7 @@ object AbilityUtils {
return null
}
if (abilityName == null) return null
- val descriptionLines = mutableListOf<Text>()
+ val descriptionLines = mutableListOf<Component>()
var manaCost: Int? = null
var cooldown: Duration? = null
while (iterator.hasNext()) {
@@ -121,7 +121,7 @@ object AbilityUtils {
)
}
- fun getAbilities(lore: List<Text>): List<ItemAbility> {
+ fun getAbilities(lore: List<Component>): List<ItemAbility> {
val iterator = lore.listIterator()
val abilities = mutableListOf<ItemAbility>()
while (iterator.hasNext()) {
diff --git a/src/main/kotlin/util/skyblock/DungeonUtil.kt b/src/main/kotlin/util/skyblock/DungeonUtil.kt
index 488b158..c4eb169 100644
--- a/src/main/kotlin/util/skyblock/DungeonUtil.kt
+++ b/src/main/kotlin/util/skyblock/DungeonUtil.kt
@@ -7,7 +7,7 @@ import moe.nea.firmament.util.TIME_PATTERN
object DungeonUtil {
val isInDungeonIsland get() = SBData.skyblockLocation == SkyBlockIsland.DUNGEON
- private val timeElapsedRegex = "Time Elapsed: $TIME_PATTERN".toRegex()
+ private val timeElapsedRegex = "Time Elapsed: (?:$TIME_PATTERN\\s*)+".toRegex()
val isInActiveDungeon get() = isInDungeonIsland && ScoreboardUtil.simplifiedScoreboardLines.any { it.matches(
timeElapsedRegex) }
@@ -21,7 +21,7 @@ object DungeonUtil {
' §7♲ §7Ironman'
' '
'Keys: §c■ §c✗ §8■ §a1x'
-'Time Elapsed: §a46s'
+'Time Elapsed: §a1m 46s'
'Cleared: §660% §8(105)'
' '
'§e[B] §b151_Dragon §e2,062§c❤'
diff --git a/src/main/kotlin/util/skyblock/ItemType.kt b/src/main/kotlin/util/skyblock/ItemType.kt
index 7a776b5..887edef 100644
--- a/src/main/kotlin/util/skyblock/ItemType.kt
+++ b/src/main/kotlin/util/skyblock/ItemType.kt
@@ -1,13 +1,12 @@
package moe.nea.firmament.util.skyblock
-import net.minecraft.item.ItemStack
+import net.minecraft.world.item.ItemStack
import moe.nea.firmament.util.directLiteralStringContent
import moe.nea.firmament.util.mc.loreAccordingToNbt
import moe.nea.firmament.util.petData
-@JvmInline
-value class ItemType private constructor(val name: String) {
+data class ItemType private constructor(val name: String) {
companion object {
fun ofName(name: String): ItemType {
return ItemType(name)
@@ -41,6 +40,7 @@ value class ItemType private constructor(val name: String) {
val SWORD = ofName("SWORD")
val DRILL = ofName("DRILL")
val PICKAXE = ofName("PICKAXE")
+ val AXE = ofName("AXE")
val GAUNTLET = ofName("GAUNTLET")
val LONGSWORD = ofName("LONG SWORD")
val EQUIPMENT = ofName("EQUIPMENT")
@@ -57,6 +57,8 @@ value class ItemType private constructor(val name: String) {
val LEGGINGS = ofName("LEGGINGS")
val HELMET = ofName("HELMET")
val BOOTS = ofName("BOOTS")
+ val SHOVEL = ofName("SHOVEL")
+
val NIL = ofName("__NIL")
/**
diff --git a/src/main/kotlin/util/skyblock/PartyUtil.kt b/src/main/kotlin/util/skyblock/PartyUtil.kt
new file mode 100644
index 0000000..46e1aa3
--- /dev/null
+++ b/src/main/kotlin/util/skyblock/PartyUtil.kt
@@ -0,0 +1,210 @@
+package moe.nea.firmament.util.skyblock
+
+import java.util.UUID
+import net.hypixel.modapi.HypixelModAPI
+import net.hypixel.modapi.packet.impl.clientbound.ClientboundPartyInfoPacket
+import net.hypixel.modapi.packet.impl.clientbound.ClientboundPartyInfoPacket.PartyRole
+import net.hypixel.modapi.packet.impl.serverbound.ServerboundPartyInfoPacket
+import org.intellij.lang.annotations.Language
+import kotlinx.coroutines.launch
+import net.minecraft.network.chat.Component
+import moe.nea.firmament.Firmament
+import moe.nea.firmament.annotations.Subscribe
+import moe.nea.firmament.apis.Routes
+import moe.nea.firmament.commands.thenExecute
+import moe.nea.firmament.commands.thenLiteral
+import moe.nea.firmament.events.CommandEvent
+import moe.nea.firmament.events.ProcessChatEvent
+import moe.nea.firmament.events.WorldReadyEvent
+import moe.nea.firmament.features.debug.DeveloperFeatures
+import moe.nea.firmament.util.ErrorUtil
+import moe.nea.firmament.util.MC
+import moe.nea.firmament.util.bold
+import moe.nea.firmament.util.boolColour
+import moe.nea.firmament.util.grey
+import moe.nea.firmament.util.tr
+import moe.nea.firmament.util.useMatch
+
+object PartyUtil {
+ object Internal {
+ val hma = HypixelModAPI.getInstance()
+
+ val handler = hma.createHandler(ClientboundPartyInfoPacket::class.java) { clientboundPartyInfoPacket ->
+ Firmament.coroutineScope.launch {
+ party = Party(clientboundPartyInfoPacket.memberMap.values.map {
+ PartyMember.fromUuid(it.uuid, it.role)
+ })
+ }
+ }
+
+ fun sendSyncPacket() {
+ hma.sendPacket(ServerboundPartyInfoPacket())
+ }
+
+ @Subscribe
+ fun onDevCommand(event: CommandEvent.SubCommand) {
+ event.subcommand(DeveloperFeatures.DEVELOPER_SUBCOMMAND) {
+ thenLiteral("party") {
+ thenLiteral("refresh") {
+ thenExecute {
+ sendSyncPacket()
+ source.sendFeedback(tr("firmament.dev.partyinfo.refresh", "Refreshing party info"))
+ }
+ }
+ thenExecute {
+ val p = party
+ val text = Component.empty()
+ text.append(
+ tr("firmament.dev.partyinfo", "Party Info: ")
+ .boolColour(p != null)
+ )
+ if (p == null) {
+ text.append(tr("firmament.dev.partyinfo.empty", "Empty Party").grey())
+ } else {
+ text.append(tr("firmament.dev.partyinfo.count", "${p.members.size} members").grey())
+ p.members.forEach {
+ text.append("\n")
+ .append(Component.literal(" - ${it.name}"))
+ .append(" (")
+ .append(
+ when (it.role) {
+ PartyRole.LEADER -> tr("firmament.dev.partyinfo.leader", "Leader").bold()
+ PartyRole.MOD -> tr("firmament.dev.partyinfo.mod", "Moderator")
+ PartyRole.MEMBER -> tr("firmament.dev.partyinfo.member", "Member")
+ }
+ )
+ .append(")")
+ }
+ }
+ source.sendFeedback(text)
+ }
+ }
+ }
+ }
+
+ object Regexes {
+ @Language("RegExp")
+ val NAME = "(\\[[^\\]]+\\] )?(?<name>[a-zA-Z0-9_]{2,16})"
+ val NAME_SECONDARY = NAME.replace("name", "name2")
+ val joinSelf = "You have joined $NAME's? party!".toPattern()
+ val joinOther = "$NAME joined the party\\.".toPattern()
+ val leaveSelf = "You left the party\\.".toPattern()
+ val disbandedEmpty =
+ "The party was disbanded because all invites expired and the party was empty\\.".toPattern()
+ val leaveOther = "$NAME has left the party\\.".toPattern()
+ val kickedOther = "$NAME has been removed from the party\\.".toPattern()
+ val kickedOtherOffline = "Kicked $NAME because they were offline\\.".toPattern()
+ val disconnectedOther = "$NAME was removed from your party because they disconnected\\.".toPattern()
+ val transferLeave = "The party was transferred to $NAME because $NAME_SECONDARY left\\.?".toPattern()
+ val transferVoluntary = "The party was transferred to $NAME by $NAME_SECONDARY\\.?".toPattern()
+ val disbanded = "$NAME has disbanded the party!".toPattern()
+ val kickedSelf = "You have been kicked from the party by $NAME ?\\.?".toPattern()
+ val partyFinderJoin = "Party Finder > $NAME joined the .* group!.*".toPattern()
+ }
+
+ fun modifyParty(
+ allowEmpty: Boolean = false,
+ modifier: (MutableList<PartyMember>) -> Unit
+ ) {
+ val oldList = party?.members ?: emptyList()
+ if (oldList.isEmpty() && !allowEmpty) return
+ party = Party(oldList.toMutableList().also(modifier))
+ }
+
+ fun MutableList<PartyMember>.modifyMember(name: String, mod: (PartyMember) -> PartyMember) {
+ val idx = indexOfFirst { it.name == name }
+ val member = if (idx < 0) {
+ PartyMember(name, PartyRole.MEMBER)
+ } else {
+ removeAt(idx)
+ }
+ add(mod(member))
+ }
+
+ fun addMemberToParty(name: String) {
+ modifyParty(true) {
+ if (it.isEmpty())
+ it.add(PartyMember(MC.playerName, PartyRole.LEADER))
+ it.add(PartyMember(name, PartyRole.MEMBER))
+ }
+ }
+
+ @Subscribe
+ fun onJoinServer(event: WorldReadyEvent) { // This event isn't perfect... Hypixel isn't ready yet when we join the server. We should probably just listen to the mod api hello packet and go from there, but this works (since you join and leave servers quite often).
+ if (party == null)
+ sendSyncPacket()
+ }
+
+ @Subscribe
+ fun onPartyRelatedMessage(event: ProcessChatEvent) {
+ Regexes.joinSelf.useMatch(event.unformattedString) {
+ sendSyncPacket()
+ }
+ Regexes.joinOther.useMatch(event.unformattedString) {
+ addMemberToParty(group("name"))
+ }
+ Regexes.leaveOther.useMatch(event.unformattedString) {
+ modifyParty { it.removeIf { it.name == group("name") } }
+ }
+ Regexes.leaveSelf.useMatch(event.unformattedString) {
+ modifyParty { it.clear() }
+ }
+ Regexes.disbandedEmpty.useMatch(event.unformattedString) {
+ modifyParty { it.clear() }
+ }
+ Regexes.kickedOther.useMatch(event.unformattedString) {
+ modifyParty { it.removeIf { it.name == group("name") } }
+ }
+ Regexes.kickedOtherOffline.useMatch(event.unformattedString) {
+ modifyParty { it.removeIf { it.name == group("name") } }
+ }
+ Regexes.disconnectedOther.useMatch(event.unformattedString) {
+ modifyParty { it.removeIf { it.name == group("name") } }
+ }
+ Regexes.transferLeave.useMatch(event.unformattedString) {
+ modifyParty {
+ it.modifyMember(group("name")) { it.copy(role = PartyRole.LEADER) }
+ it.removeIf { it.name == group("name2") }
+ }
+ }
+ Regexes.transferVoluntary.useMatch(event.unformattedString) {
+ modifyParty {
+ it.modifyMember(group("name")) { it.copy(role = PartyRole.LEADER) }
+ it.modifyMember(group("name2")) { it.copy(role = PartyRole.MOD) }
+ }
+ }
+ Regexes.disbanded.useMatch(event.unformattedString) {
+ modifyParty { it.clear() }
+ }
+ Regexes.kickedSelf.useMatch(event.unformattedString) {
+ modifyParty { it.clear() }
+ }
+ Regexes.partyFinderJoin.useMatch(event.unformattedString) {
+ addMemberToParty(group("name"))
+ }
+ }
+ }
+
+ data class Party(
+ val members: List<PartyMember>
+ )
+
+ data class PartyMember(
+ val name: String,
+ val role: PartyRole
+ ) {
+ companion object {
+ suspend fun fromUuid(uuid: UUID, role: PartyRole = PartyRole.MEMBER): PartyMember {
+ return PartyMember(
+ ErrorUtil.notNullOr(
+ Routes.getPlayerNameForUUID(uuid),
+ "Could not find username for player $uuid"
+ ) { "Ghost" },
+ role
+ )
+ }
+ }
+ }
+
+ var party: Party? = null
+}
diff --git a/src/main/kotlin/util/skyblock/Rarity.kt b/src/main/kotlin/util/skyblock/Rarity.kt
index b19f371..95e5d87 100644
--- a/src/main/kotlin/util/skyblock/Rarity.kt
+++ b/src/main/kotlin/util/skyblock/Rarity.kt
@@ -7,10 +7,10 @@ import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
-import net.minecraft.item.ItemStack
-import net.minecraft.text.Style
-import net.minecraft.text.Text
-import net.minecraft.util.Formatting
+import net.minecraft.world.item.ItemStack
+import net.minecraft.network.chat.Style
+import net.minecraft.network.chat.Component
+import net.minecraft.ChatFormatting
import moe.nea.firmament.util.StringUtil.words
import moe.nea.firmament.util.collections.lastNotNullOfOrNull
import moe.nea.firmament.util.mc.loreAccordingToNbt
@@ -31,6 +31,7 @@ enum class Rarity(vararg altNames: String) {
SUPREME,
SPECIAL,
VERY_SPECIAL,
+ ULTIMATE,
UNKNOWN
;
@@ -48,22 +49,23 @@ enum class Rarity(vararg altNames: String) {
}
val names = setOf(name) + altNames
- val text: Text get() = Text.literal(name).setStyle(Style.EMPTY.withColor(colourMap[this]))
+ val text: Component get() = Component.literal(name).setStyle(Style.EMPTY.withColor(colourMap[this]))
val neuRepoRarity: RepoRarity? = RepoRarity.entries.find { it.name == name }
companion object {
// TODO: inline those formattings as fields
val colourMap = 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,
+ Rarity.COMMON to ChatFormatting.WHITE,
+ Rarity.UNCOMMON to ChatFormatting.GREEN,
+ Rarity.RARE to ChatFormatting.BLUE,
+ Rarity.EPIC to ChatFormatting.DARK_PURPLE,
+ Rarity.LEGENDARY to ChatFormatting.GOLD,
+ Rarity.MYTHIC to ChatFormatting.LIGHT_PURPLE,
+ Rarity.DIVINE to ChatFormatting.AQUA,
+ Rarity.SPECIAL to ChatFormatting.RED,
+ Rarity.VERY_SPECIAL to ChatFormatting.RED,
+ Rarity.SUPREME to ChatFormatting.DARK_RED,
+ Rarity.ULTIMATE to ChatFormatting.DARK_RED,
)
val byName = entries.flatMap { en -> en.names.map { it to en } }.toMap()
val fromNeuRepo = entries.associateBy { it.neuRepoRarity }
@@ -87,7 +89,7 @@ enum class Rarity(vararg altNames: String) {
fun fromPetItem(itemStack: ItemStack): Rarity? =
itemStack.petData?.tier?.let(::fromNeuRepo)
- fun fromLore(lore: List<Text>): Rarity? =
+ fun fromLore(lore: List<Component>): Rarity? =
lore.lastNotNullOfOrNull {
it.unformattedString.words()
.firstNotNullOfOrNull(::fromString)
diff --git a/src/main/kotlin/util/skyblock/SBItemUtil.kt b/src/main/kotlin/util/skyblock/SBItemUtil.kt
index 3901b60..619a10b 100644
--- a/src/main/kotlin/util/skyblock/SBItemUtil.kt
+++ b/src/main/kotlin/util/skyblock/SBItemUtil.kt
@@ -1,12 +1,12 @@
package moe.nea.firmament.util.skyblock
-import net.minecraft.item.ItemStack
+import net.minecraft.world.item.ItemStack
import moe.nea.firmament.util.mc.loreAccordingToNbt
import moe.nea.firmament.util.unformattedString
object SBItemUtil {
fun ItemStack.getSearchName(): String {
- val name = this.name.unformattedString
+ val name = this.hoverName.unformattedString
if (name.contains("Enchanted Book")) {
val enchant = loreAccordingToNbt.firstOrNull()?.unformattedString
if (enchant != null) return enchant
diff --git a/src/main/kotlin/util/skyblock/SackUtil.kt b/src/main/kotlin/util/skyblock/SackUtil.kt
index fd67c44..a69309b 100644
--- a/src/main/kotlin/util/skyblock/SackUtil.kt
+++ b/src/main/kotlin/util/skyblock/SackUtil.kt
@@ -2,15 +2,18 @@ package moe.nea.firmament.util.skyblock
import kotlinx.serialization.Serializable
import kotlinx.serialization.serializer
-import net.minecraft.client.gui.screen.ingame.GenericContainerScreen
-import net.minecraft.text.HoverEvent
-import net.minecraft.text.Text
+import net.minecraft.client.gui.screens.inventory.ContainerScreen
+import net.minecraft.network.chat.HoverEvent
+import net.minecraft.network.chat.Component
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.ChestInventoryUpdateEvent
import moe.nea.firmament.events.ProcessChatEvent
+import moe.nea.firmament.gui.config.storage.ConfigFixEvent
+import moe.nea.firmament.gui.config.storage.ConfigStorageClass
import moe.nea.firmament.repo.ItemNameLookup
import moe.nea.firmament.util.SHORT_NUMBER_FORMAT
import moe.nea.firmament.util.SkyblockId
+import moe.nea.firmament.util.data.Config
import moe.nea.firmament.util.data.ProfileSpecificDataHolder
import moe.nea.firmament.util.mc.displayNameAccordingToNbt
import moe.nea.firmament.util.mc.iterableView
@@ -28,18 +31,26 @@ object SackUtil {
// val sackTypes:
)
- object Store : ProfileSpecificDataHolder<SackContents>(serializer(), "Sacks", ::SackContents)
+ @Config
+ object Store : ProfileSpecificDataHolder<SackContents>(serializer(), "sacks", ::SackContents)
+
+ @Subscribe
+ fun onConfigFix(event: ConfigFixEvent) {
+ event.on(996, ConfigStorageClass.PROFILE) {
+ move("Sacks", "sacks")
+ }
+ }
val items get() = Store.data?.contents ?: mutableMapOf()
val storedRegex = "^Stored: (?<stored>$SHORT_NUMBER_FORMAT)/(?<max>$SHORT_NUMBER_FORMAT)$".toPattern()
@Subscribe
fun storeDataFromInventory(event: ChestInventoryUpdateEvent) {
- val screen = event.inventory as? GenericContainerScreen ?: return
+ val screen = event.inventory as? ContainerScreen ?: return
if (!screen.title.unformattedString.endsWith(" Sack")) return
- val inv = screen.screenHandler?.inventory ?: return
- if (inv.size() < 18) return
- val backSlot = inv.getStack(inv.size() - 5)
+ val inv = screen.menu?.container ?: return
+ if (inv.containerSize < 18) return
+ val backSlot = inv.getItem(inv.containerSize - 5)
if (backSlot.displayNameAccordingToNbt.unformattedString != "Go Back") return
if (backSlot.loreAccordingToNbt.map { it.unformattedString } != listOf("To Sack of Sacks")) return
for (itemStack in inv.iterableView) {
@@ -63,7 +74,7 @@ object SackUtil {
getUpdatesFromMessage(event.text)
}
- fun getUpdatesFromMessage(text: Text): List<SackUpdate> {
+ fun getUpdatesFromMessage(text: Component): List<SackUpdate> {
val update = ChatUpdate()
text.siblings.forEach(update::updateFromHoverText)
return update.updates
@@ -91,9 +102,9 @@ object SackUtil {
}
}
- fun updateFromHoverText(text: Text) {
+ fun updateFromHoverText(text: Component) {
text.siblings.forEach(::updateFromHoverText)
- val hoverText = text.style.hoverEvent?.getValue(HoverEvent.Action.SHOW_TEXT) ?: return
+ val hoverText = (text.style.hoverEvent as? HoverEvent.ShowText)?.value ?: return
val cleanedText = hoverText.unformattedString
if (cleanedText.startsWith("Added items:\n")) {
if (!foundAdded) {
diff --git a/src/main/kotlin/util/skyblock/ScreenIdentification.kt b/src/main/kotlin/util/skyblock/ScreenIdentification.kt
new file mode 100644
index 0000000..ff725fa
--- /dev/null
+++ b/src/main/kotlin/util/skyblock/ScreenIdentification.kt
@@ -0,0 +1,52 @@
+package moe.nea.firmament.util.skyblock
+
+import net.minecraft.client.gui.screens.Screen
+import net.minecraft.client.gui.screens.inventory.ContainerScreen
+import moe.nea.firmament.util.mc.displayNameAccordingToNbt
+import moe.nea.firmament.util.mc.loreAccordingToNbt
+import moe.nea.firmament.util.unformattedString
+
+
+object ScreenIdentification {
+ private var lastScreen: Screen? = null
+ private var lastScreenType: ScreenType? = null
+
+ fun getType(screen: Screen?): ScreenType? {
+ if (screen == null) return null
+ if (screen !== lastScreen) {
+ lastScreenType = ScreenType.entries
+ .find { it.detector(screen) }
+ lastScreen = screen
+ }
+ return lastScreenType
+ }
+}
+
+enum class ScreenType(val detector: (Screen) -> Boolean) {
+ BAZAAR_ANY({
+ it is ContainerScreen && (
+ it.menu.getSlot(it.menu.rowCount * 9 - 4)
+ .item
+ .displayNameAccordingToNbt
+ .unformattedString == "Manage Orders"
+ || it.menu.getSlot(it.menu.rowCount * 9 - 5)
+ .item
+ .loreAccordingToNbt
+ .any {
+ it.unformattedString == "To Bazaar"
+ })
+ }),
+ ENCHANTMENT_GUIDE({
+ it.title.unformattedString.endsWith("Enchantments Guide")
+ }),
+ SUPER_PAIRS({
+ it.title.unformattedString.startsWith("Superpairs")
+ }),
+ EXPERIMENTATION_RNG_METER({
+ it.title.unformattedString.contains("Experimentation Table RNG")
+ }),
+ DYE_COMPENDIUM({
+ it.title.unformattedString.contains("Dye Compendium")
+ })
+}
+
diff --git a/src/main/kotlin/util/skyblock/SkyBlockItems.kt b/src/main/kotlin/util/skyblock/SkyBlockItems.kt
index ca2b17b..5f4acd4 100644
--- a/src/main/kotlin/util/skyblock/SkyBlockItems.kt
+++ b/src/main/kotlin/util/skyblock/SkyBlockItems.kt
@@ -3,14 +3,25 @@ package moe.nea.firmament.util.skyblock
import moe.nea.firmament.util.SkyblockId
object SkyBlockItems {
+ val COINS = SkyblockId("SKYBLOCK_COIN")
val ROTTEN_FLESH = SkyblockId("ROTTEN_FLESH")
val ENCHANTED_DIAMOND = SkyblockId("ENCHANTED_DIAMOND")
val DIAMOND = SkyblockId("DIAMOND")
val ANCESTRAL_SPADE = SkyblockId("ANCESTRAL_SPADE")
+ val ARCHAIC_SPADE = SkyblockId("ARCHAIC_SPADE")
+ val DEIFIC_SPADE = SkyblockId("DEIFIC_SPADE")
val REFORGE_ANVIL = SkyblockId("REFORGE_ANVIL")
val SLICE_OF_BLUEBERRY_CAKE = SkyblockId("SLICE_OF_BLUEBERRY_CAKE")
val SLICE_OF_CHEESECAKE = SkyblockId("SLICE_OF_CHEESECAKE")
val SLICE_OF_GREEN_VELVET_CAKE = SkyblockId("SLICE_OF_GREEN_VELVET_CAKE")
val SLICE_OF_RED_VELVET_CAKE = SkyblockId("SLICE_OF_RED_VELVET_CAKE")
val SLICE_OF_STRAWBERRY_SHORTCAKE = SkyblockId("SLICE_OF_STRAWBERRY_SHORTCAKE")
+ val ASPECT_OF_THE_VOID = SkyblockId("ASPECT_OF_THE_VOID")
+ val ASPECT_OF_THE_END = SkyblockId("ASPECT_OF_THE_END")
+ val BONE_BOOMERANG = SkyblockId("BONE_BOOMERANG")
+ val STARRED_BONE_BOOMERANG = SkyblockId("STARRED_BONE_BOOMERANG")
+ val TRIBAL_SPEAR = SkyblockId("TRIBAL_SPEAR")
+ val BLOCK_ZAPPER = SkyblockId("BLOCK_ZAPPER")
+ val HUNTING_TOOLKIT = SkyblockId("HUNTING_TOOLKIT")
+ val ETHERWARP_CONDUIT = SkyblockId("ETHERWARP_CONDUIT")
}
diff --git a/src/main/kotlin/util/skyblock/TabListAPI.kt b/src/main/kotlin/util/skyblock/TabListAPI.kt
new file mode 100644
index 0000000..43722e0
--- /dev/null
+++ b/src/main/kotlin/util/skyblock/TabListAPI.kt
@@ -0,0 +1,41 @@
+package moe.nea.firmament.util.skyblock
+
+import org.intellij.lang.annotations.Language
+import net.minecraft.network.chat.Component
+import moe.nea.firmament.util.StringUtil.title
+import moe.nea.firmament.util.StringUtil.unwords
+import moe.nea.firmament.util.mc.MCTabListAPI
+import moe.nea.firmament.util.unformattedString
+
+object TabListAPI {
+
+ fun getWidgetLines(widgetName: WidgetName, includeTitle: Boolean = false, from: MCTabListAPI.CurrentTabList = MCTabListAPI.currentTabList): List<Component> {
+ return from.body
+ .dropWhile { !widgetName.matchesTitle(it) }
+ .takeWhile { it.string.isNotBlank() && !it.string.startsWith(" ") }
+ .let { if (includeTitle) it else it.drop(1) }
+ }
+
+ enum class WidgetName(regex: Regex?) {
+ COMMISSIONS,
+ SKILLS("Skills:( .*)?"),
+ PROFILE("Profile: (.*)"),
+ COLLECTION,
+ ESSENCE,
+ PET
+ ;
+
+ fun matchesTitle(it: Component): Boolean {
+ return regex.matches(it.unformattedString)
+ }
+
+ constructor() : this(null)
+ constructor(@Language("RegExp") regex: String) : this(Regex(regex))
+
+ val label =
+ name.split("_").map { it.lowercase().title() }.unwords()
+ val regex = regex ?: Regex.fromLiteral("$label:")
+
+ }
+
+}
diff --git a/src/main/kotlin/util/textutil.kt b/src/main/kotlin/util/textutil.kt
index 806f61e..d8b2592 100644
--- a/src/main/kotlin/util/textutil.kt
+++ b/src/main/kotlin/util/textutil.kt
@@ -1,17 +1,17 @@
package moe.nea.firmament.util
import java.util.Optional
-import net.minecraft.text.ClickEvent
-import net.minecraft.text.HoverEvent
-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 net.minecraft.network.chat.ClickEvent
+import net.minecraft.network.chat.HoverEvent
+import net.minecraft.network.chat.MutableComponent
+import net.minecraft.util.FormattedCharSequence
+import net.minecraft.network.chat.contents.PlainTextContents
+import net.minecraft.network.chat.FormattedText
+import net.minecraft.network.chat.Style
+import net.minecraft.network.chat.Component
+import net.minecraft.network.chat.TextColor
+import net.minecraft.network.chat.contents.TranslatableContents
+import net.minecraft.ChatFormatting
val formattingChars = "kmolnrKMOLNR".toSet()
@@ -36,15 +36,15 @@ fun CharSequence.removeColorCodes(keepNonColorCodes: Boolean = false): String {
return stringBuffer.toString()
}
-fun OrderedText.reconstitute(): MutableText {
- val base = Text.literal("")
+fun FormattedCharSequence.reconstitute(): MutableComponent {
+ val base = Component.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))
+ base.append(Component.literal(text.toString()).setStyle(lastColorCode))
lastColorCode = style
text.clear()
}
@@ -52,19 +52,20 @@ fun OrderedText.reconstitute(): MutableText {
true
}
if (text.isNotEmpty())
- base.append(Text.literal(text.toString()).setStyle(lastColorCode))
+ base.append(Component.literal(text.toString()).setStyle(lastColorCode))
return base
}
-fun StringVisitable.reconstitute(): MutableText {
- val base = Text.literal("")
+
+fun FormattedText.reconstitute(): MutableComponent {
+ val base = Component.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))
+ base.append(Component.literal(text.toString()).setStyle(lastColorCode))
lastColorCode = style
text.clear()
}
@@ -72,84 +73,122 @@ fun StringVisitable.reconstitute(): MutableText {
Optional.empty<Unit>()
}, Style.EMPTY)
if (text.isNotEmpty())
- base.append(Text.literal(text.toString()).setStyle(lastColorCode))
+ base.append(Component.literal(text.toString()).setStyle(lastColorCode))
return base
}
-val Text.unformattedString: String
+val Component.unformattedString: String
get() = string.removeColorCodes() // TODO: maybe shortcircuit this with .visit
-val Text.directLiteralStringContent: String? get() = (this.content as? PlainTextContent)?.string()
+val Component.directLiteralStringContent: String? get() = (this.contents as? PlainTextContents)?.text()
-fun Text.getLegacyFormatString() =
+fun Component.getLegacyFormatString(trimmed: Boolean = false): String =
run {
+ var lastCode = "§r"
val sb = StringBuilder()
+ fun appendCode(code: String) {
+ if (code != lastCode || !trimmed) {
+ sb.append(code)
+ lastCode = code
+ }
+ }
for (component in iterator()) {
- sb.append(component.style.color?.toChatFormatting()?.toString() ?: "§r")
+ if (component.directLiteralStringContent.isNullOrEmpty() && component.siblings.isEmpty()) {
+ continue
+ }
+ appendCode(component.style.let { style ->
+ var color = style.color?.toChatFormatting()?.toString() ?: "§r"
+ if (style.isBold)
+ color += LegacyFormattingCode.BOLD.formattingCode
+ if (style.isItalic)
+ color += LegacyFormattingCode.ITALIC.formattingCode
+ if (style.isUnderlined)
+ color += LegacyFormattingCode.UNDERLINE.formattingCode
+ if (style.isObfuscated)
+ color += LegacyFormattingCode.OBFUSCATED.formattingCode
+ if (style.isStrikethrough)
+ color += LegacyFormattingCode.STRIKETHROUGH.formattingCode
+ color
+ })
sb.append(component.directLiteralStringContent)
- sb.append("§r")
+ if (!trimmed)
+ appendCode("§r")
}
sb.toString()
+ }.also {
+ var it = it
+ if (trimmed) {
+ it = it.removeSuffix("§r")
+ if (it.length == 2 && it.startsWith("§"))
+ it = ""
+ }
+ it
}
-private val textColorLUT = Formatting.entries
- .mapNotNull { formatting -> formatting.colorValue?.let { it to formatting } }
+private val textColorLUT = ChatFormatting.entries
+ .mapNotNull { formatting -> formatting.color?.let { it to formatting } }
.toMap()
-fun TextColor.toChatFormatting(): Formatting? {
- return textColorLUT[this.rgb]
+fun TextColor.toChatFormatting(): ChatFormatting? {
+ return textColorLUT[this.value]
}
-fun Text.iterator(): Sequence<Text> {
+fun Component.iterator(): Sequence<Component> {
return sequenceOf(this) + siblings.asSequence()
.flatMap { it.iterator() } // TODO: in theory we want to properly inherit styles here
}
-fun Text.allSiblings(): List<Text> = listOf(this) + siblings.flatMap { it.allSiblings() }
+fun Component.allSiblings(): List<Component> = listOf(this) + siblings.flatMap { it.allSiblings() }
-fun MutableText.withColor(formatting: Formatting): MutableText = this.styled {
+fun MutableComponent.withColor(formatting: ChatFormatting): MutableComponent = this.withStyle {
it.withColor(formatting)
.withItalic(false)
.withBold(false)
}
-fun MutableText.blue() = withColor(Formatting.BLUE)
-fun MutableText.aqua() = withColor(Formatting.AQUA)
-fun MutableText.lime() = withColor(Formatting.GREEN)
-fun MutableText.darkGreen() = withColor(Formatting.DARK_GREEN)
-fun MutableText.purple() = withColor(Formatting.DARK_PURPLE)
-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) }
-fun MutableText.hover(text: Text): MutableText = styled {it.withHoverEvent(HoverEvent(HoverEvent.Action.SHOW_TEXT, text))}
-
-
-fun MutableText.clickCommand(command: String): MutableText {
+fun MutableComponent.blue() = withColor(ChatFormatting.BLUE)
+fun MutableComponent.aqua() = withColor(ChatFormatting.AQUA)
+fun MutableComponent.lime() = withColor(ChatFormatting.GREEN)
+fun MutableComponent.darkGreen() = withColor(ChatFormatting.DARK_GREEN)
+fun MutableComponent.purple() = withColor(ChatFormatting.DARK_PURPLE)
+fun MutableComponent.pink() = withColor(ChatFormatting.LIGHT_PURPLE)
+fun MutableComponent.yellow() = withColor(ChatFormatting.YELLOW)
+fun MutableComponent.gold() = withColor(ChatFormatting.GOLD)
+fun MutableComponent.grey() = withColor(ChatFormatting.GRAY)
+fun MutableComponent.darkGrey() = withColor(ChatFormatting.DARK_GRAY)
+fun MutableComponent.red() = withColor(ChatFormatting.RED)
+fun MutableComponent.white() = withColor(ChatFormatting.WHITE)
+fun MutableComponent.bold(): MutableComponent = withStyle { it.withBold(true) }
+fun MutableComponent.hover(text: Component): MutableComponent = withStyle { it.withHoverEvent(HoverEvent.ShowText(text)) }
+fun MutableComponent.boolColour(
+ bool: Boolean,
+ ifTrue: ChatFormatting = ChatFormatting.GREEN,
+ ifFalse: ChatFormatting = ChatFormatting.DARK_RED
+) =
+ if (bool) withColor(ifTrue) else withColor(ifFalse)
+
+fun MutableComponent.clickCommand(command: String): MutableComponent {
require(command.startsWith("/"))
- return this.styled {
- it.withClickEvent(ClickEvent(ClickEvent.Action.RUN_COMMAND, command))
+ return this.withStyle {
+ it.withClickEvent(ClickEvent.RunCommand(command))
}
}
-fun MutableText.prepend(text: Text): MutableText {
+fun MutableComponent.prepend(text: Component): MutableComponent {
siblings.addFirst(text)
return this
}
-fun Text.transformEachRecursively(function: (Text) -> Text): Text {
- val c = this.content
- if (c is TranslatableTextContent) {
- return Text.translatableWithFallback(c.key, c.fallback, *c.args.map {
- (if (it is Text) it else Text.literal(it.toString())).transformEachRecursively(function)
+fun Component.transformEachRecursively(function: (Component) -> Component): Component {
+ val c = this.contents
+ if (c is TranslatableContents) {
+ return Component.translatableWithFallback(c.key, c.fallback, *c.args.map {
+ (it as? Component ?: Component.literal(it.toString())).transformEachRecursively(function)
}.toTypedArray()).also { new ->
new.style = this.style
new.siblings.clear()
+ val new = function(new)
this.siblings.forEach { child ->
new.siblings.add(child.transformEachRecursively(function))
}
@@ -162,6 +201,16 @@ fun Text.transformEachRecursively(function: (Text) -> Text): Text {
}
}
-fun tr(key: String, default: String): MutableText = error("Compiler plugin did not run.")
-fun trResolved(key: String, vararg args: Any): MutableText = Text.stringifiedTranslatable(key, *args)
+fun tr(key: String, default: String): MutableComponent = error("Compiler plugin did not run.")
+fun trResolved(key: String, vararg args: Any): MutableComponent = Component.translatableEscape(key, *args)
+fun titleCase(str: String): String {
+ return str
+ .lowercase()
+ .replace("_", " ")
+ .split(" ")
+ .joinToString(" ") { word ->
+ word.replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }
+ }
+}
+
diff --git a/src/main/kotlin/util/uuid.kt b/src/main/kotlin/util/uuid.kt
index cccfdd2..14aa83d 100644
--- a/src/main/kotlin/util/uuid.kt
+++ b/src/main/kotlin/util/uuid.kt
@@ -3,6 +3,12 @@ package moe.nea.firmament.util
import java.math.BigInteger
import java.util.UUID
+fun parsePotentiallyDashlessUUID(unknownFormattedUUID: String): UUID {
+ if ("-" in unknownFormattedUUID)
+ return UUID.fromString(unknownFormattedUUID)
+ return parseDashlessUUID(unknownFormattedUUID)
+}
+
fun parseDashlessUUID(dashlessUuid: String): UUID {
val most = BigInteger(dashlessUuid.substring(0, 16), 16)
val least = BigInteger(dashlessUuid.substring(16, 32), 16)