aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/features/misc
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/kotlin/features/misc')
-rw-r--r--src/main/kotlin/features/misc/CustomCapes.kt172
-rw-r--r--src/main/kotlin/features/misc/Devs.kt42
-rw-r--r--src/main/kotlin/features/misc/Hud.kt75
-rw-r--r--src/main/kotlin/features/misc/LicenseViewer.kt136
-rw-r--r--src/main/kotlin/features/misc/ModAnnouncer.kt80
5 files changed, 505 insertions, 0 deletions
diff --git a/src/main/kotlin/features/misc/CustomCapes.kt b/src/main/kotlin/features/misc/CustomCapes.kt
new file mode 100644
index 0000000..fe54e6b
--- /dev/null
+++ b/src/main/kotlin/features/misc/CustomCapes.kt
@@ -0,0 +1,172 @@
+package moe.nea.firmament.features.misc
+
+import util.render.CustomRenderPipelines
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.seconds
+import net.minecraft.client.player.AbstractClientPlayer
+import net.minecraft.client.renderer.RenderType
+import com.mojang.blaze3d.vertex.VertexConsumer
+import net.minecraft.client.renderer.MultiBufferSource
+import net.minecraft.client.renderer.entity.state.AvatarRenderState
+import com.mojang.blaze3d.vertex.PoseStack
+import net.minecraft.world.entity.player.PlayerSkin
+import net.minecraft.core.ClientAsset
+import net.minecraft.resources.ResourceLocation
+import moe.nea.firmament.Firmament
+import moe.nea.firmament.util.MC
+import moe.nea.firmament.util.TimeMark
+import moe.nea.firmament.util.data.Config
+import moe.nea.firmament.util.data.ManagedConfig
+import moe.nea.firmament.util.mc.CustomRenderPassHelper
+
+object CustomCapes {
+ val identifier: String
+ get() = "developer-capes"
+
+ @Config
+ object TConfig : ManagedConfig(identifier, Category.DEV) {
+ val showCapes by toggle("show-cape") { true }
+ }
+
+ interface CustomCapeRenderer {
+ fun replaceRender(
+ renderLayer: RenderType,
+ vertexConsumerProvider: MultiBufferSource,
+ matrixStack: PoseStack,
+ model: (VertexConsumer) -> Unit
+ )
+ }
+
+ data class TexturedCapeRenderer(
+ val location: ResourceLocation
+ ) : CustomCapeRenderer {
+ override fun replaceRender(
+ renderLayer: RenderType,
+ vertexConsumerProvider: MultiBufferSource,
+ matrixStack: PoseStack,
+ model: (VertexConsumer) -> Unit
+ ) {
+ model(vertexConsumerProvider.getBuffer(RenderType.entitySolid(location)))
+ }
+ }
+
+ data class ParallaxedHighlightCapeRenderer(
+ val template: ResourceLocation,
+ val background: ResourceLocation,
+ val overlay: ResourceLocation,
+ val animationSpeed: Duration,
+ ) : CustomCapeRenderer {
+ override fun replaceRender(
+ renderLayer: RenderType,
+ vertexConsumerProvider: MultiBufferSource,
+ matrixStack: PoseStack,
+ model: (VertexConsumer) -> Unit
+ ) {
+ val animationValue = (startTime.passedTime() / animationSpeed).mod(1F)
+ CustomRenderPassHelper(
+ { "Firmament Cape Renderer" },
+ renderLayer.mode(),
+ renderLayer.format(),
+ MC.instance.mainRenderTarget,
+ true,
+ ).use { renderPass ->
+ renderPass.setPipeline(CustomRenderPipelines.PARALLAX_CAPE_SHADER)
+ renderPass.setAllDefaultUniforms()
+ renderPass.setUniform("Animation", 4) {
+ it.putFloat(animationValue.toFloat())
+ }
+ renderPass.bindSampler("Sampler0", template)
+ renderPass.bindSampler("Sampler1", background)
+ renderPass.bindSampler("Sampler3", overlay)
+ renderPass.uploadVertices(2048, model)
+ renderPass.draw()
+ }
+ }
+ }
+
+ interface CapeStorage {
+ companion object {
+ @JvmStatic
+ fun cast(playerEntityRenderState: AvatarRenderState) =
+ playerEntityRenderState as CapeStorage
+
+ }
+
+ var cape_firmament: CustomCape?
+ }
+
+ data class CustomCape(
+ val id: String,
+ val label: String,
+ val render: CustomCapeRenderer,
+ )
+
+ enum class AllCapes(val label: String, val render: CustomCapeRenderer) {
+ FIRMAMENT_ANIMATED(
+ "Animated Firmament", ParallaxedHighlightCapeRenderer(
+ Firmament.identifier("textures/cape/parallax_template.png"),
+ Firmament.identifier("textures/cape/parallax_background.png"),
+ Firmament.identifier("textures/cape/firmament_star.png"),
+ 110.seconds
+ )
+ ),
+ UNPLEASANT_GRADIENT(
+ "unpleasant_gradient",
+ TexturedCapeRenderer(Firmament.identifier("textures/cape/unpleasant_gradient.png"))
+ ),
+ FURFSKY_STATIC(
+ "FurfSky",
+ TexturedCapeRenderer(Firmament.identifier("textures/cape/fsr_static.png"))
+ ),
+
+ FIRMAMENT_STATIC(
+ "Firmament",
+ TexturedCapeRenderer(Firmament.identifier("textures/cape/firm_static.png"))
+ ),
+ HYPIXEL_PLUS(
+ "Hypixel+",
+ TexturedCapeRenderer(Firmament.identifier("textures/cape/h_plus.png"))
+ ),
+ ;
+
+ val cape = CustomCape(name, label, render)
+ }
+
+ val byId = AllCapes.entries.associateBy { it.cape.id }
+ val byUuid =
+ listOf(
+ listOf(
+ Devs.nea to AllCapes.UNPLEASANT_GRADIENT,
+ Devs.kath to AllCapes.FIRMAMENT_STATIC,
+ Devs.jani to AllCapes.FIRMAMENT_ANIMATED,
+ Devs.nat to AllCapes.FIRMAMENT_ANIMATED,
+ Devs.HPlus.ic22487 to AllCapes.HYPIXEL_PLUS,
+ ),
+ Devs.FurfSky.all.map { it to AllCapes.FURFSKY_STATIC },
+ ).flatten().flatMap { (dev, cape) -> dev.uuids.map { it to cape.cape } }.toMap()
+
+ @JvmStatic
+ fun addCapeData(
+ player: AbstractClientPlayer,
+ playerEntityRenderState: AvatarRenderState
+ ) {
+ if (true) return // TODO: see capefeaturerenderer mixin
+ val cape = if (TConfig.showCapes) byUuid[player.uuid] else null
+ val capeStorage = CapeStorage.cast(playerEntityRenderState)
+ if (cape == null) {
+ capeStorage.cape_firmament = null
+ } else {
+ capeStorage.cape_firmament = cape
+ playerEntityRenderState.skin = PlayerSkin(
+ playerEntityRenderState.skin.body,
+ ClientAsset.ResourceTexture(Firmament.identifier("placeholder/fake_cape"), Firmament.identifier("placeholder/fake_cape")),
+ playerEntityRenderState.skin.elytra,
+ playerEntityRenderState.skin.model,
+ playerEntityRenderState.skin.secure,
+ )
+ playerEntityRenderState.showCape = true
+ }
+ }
+
+ val startTime = TimeMark.now()
+}
diff --git a/src/main/kotlin/features/misc/Devs.kt b/src/main/kotlin/features/misc/Devs.kt
new file mode 100644
index 0000000..b9f7e03
--- /dev/null
+++ b/src/main/kotlin/features/misc/Devs.kt
@@ -0,0 +1,42 @@
+package moe.nea.firmament.features.misc
+
+import java.util.UUID
+
+object Devs {
+ data class Dev(
+ val uuids: List<UUID>,
+ ) {
+ constructor(vararg uuid: UUID) : this(uuid.toList())
+ constructor(vararg uuid: String) : this(uuid.map { UUID.fromString(it) })
+ }
+
+ val nea = Dev("d3cb85e2-3075-48a1-b213-a9bfb62360c1", "842204e6-6880-487b-ae5a-0595394f9948")
+ val kath = Dev("add71246-c46e-455c-8345-c129ea6f146c", "b491990d-53fd-4c5f-a61e-19d58cc7eddf")
+ val jani = Dev("8a9f1841-48e9-48ed-b14f-76a124e6c9df")
+ val nat = Dev("168300e6-4e74-4a6d-89a0-7b7cf8ad6a7d", "06914e9d-7bc2-4cb7-b112-62c4cc958d96")
+
+ object FurfSky {
+ val smolegit = Dev("02b38b96-eb19-405a-b319-d6bc00b26ab3")
+ val itsCen = Dev("ada70b5a-ac37-49d2-b18c-1351672f8051")
+ val webster = Dev("02166f1b-9e8d-4e48-9e18-ea7a4499492d")
+ val vrachel = Dev("22e98637-ba97-4b6b-a84f-fb57a461ce43")
+ val cunuduh = Dev("2a15e3b3-c46e-4718-b907-166e1ab2efdc")
+ val eiiies = Dev("2ae162f2-81a7-4f91-a4b2-104e78a0a7e1")
+ val june = Dev("2584a4e3-f917-4493-8ced-618391f3b44f")
+ val denasu = Dev("313cbd25-8ade-4e41-845c-5cab555a30c9")
+ val libyKiwii = Dev("4265c52e-bd6f-4d3c-9cf6-bdfc8fb58023")
+ val madeleaan = Dev("bcb119a3-6000-4324-bda1-744f00c44b31")
+ val turtleSP = Dev("f1ca1934-a582-4723-8283-89921d008657")
+ val papayamm = Dev("ae0eea30-f6a2-40fe-ac17-9c80b3423409")
+ val persuasiveViksy = Dev("ba7ac144-28e0-4f55-a108-1a72fe744c9e")
+ val all = listOf(
+ smolegit, itsCen, webster, vrachel, cunuduh, eiiies,
+ june, denasu, libyKiwii, madeleaan, turtleSP, papayamm,
+ persuasiveViksy
+ )
+ }
+ object HPlus {
+ val ic22487 = Dev("ab2be3b2-bb75-4aaa-892d-9fff5a7e3953")
+ }
+
+}
diff --git a/src/main/kotlin/features/misc/Hud.kt b/src/main/kotlin/features/misc/Hud.kt
new file mode 100644
index 0000000..bbe8044
--- /dev/null
+++ b/src/main/kotlin/features/misc/Hud.kt
@@ -0,0 +1,75 @@
+package moe.nea.firmament.features.misc
+
+import org.joml.Vector2i
+import net.minecraft.client.multiplayer.PlayerInfo
+import net.minecraft.network.chat.Component
+import moe.nea.firmament.annotations.Subscribe
+import moe.nea.firmament.events.HudRenderEvent
+import moe.nea.firmament.util.MC
+import moe.nea.firmament.util.data.Config
+import moe.nea.firmament.util.data.ManagedConfig
+import moe.nea.firmament.util.tr
+
+object Hud {
+ val identifier: String
+ get() = "hud"
+
+ @Config
+ object TConfig : ManagedConfig(identifier, Category.MISC) {
+ var dayCount by toggle("day-count") { false }
+ val dayCountHud by position("day-count-hud", 80, 10) { Vector2i() }
+ var fpsCount by toggle("fps-count") { false }
+ val fpsCountHud by position("fps-count-hud", 80, 10) { Vector2i() }
+ var pingCount by toggle("ping-count") { false }
+ val pingCountHud by position("ping-count-hud", 80, 10) { Vector2i() }
+ }
+
+ @Subscribe
+ fun onRenderHud(it: HudRenderEvent) {
+ if (TConfig.dayCount) {
+ it.context.pose().pushMatrix()
+ TConfig.dayCountHud.applyTransformations(it.context.pose())
+ val day = (MC.world?.dayTime ?: 0L) / 24000
+ it.context.drawString(
+ MC.font,
+ Component.literal(String.format(tr("firmament.config.hud.day-count-hud.display", "Day: %s").string, day)),
+ 36,
+ MC.font.lineHeight,
+ -1,
+ true
+ )
+ it.context.pose().popMatrix()
+ }
+
+ if (TConfig.fpsCount) {
+ it.context.pose().pushMatrix()
+ TConfig.fpsCountHud.applyTransformations(it.context.pose())
+ it.context.drawString(
+ MC.font, Component.literal(
+ String.format(
+ tr("firmament.config.hud.fps-count-hud.display", "FPS: %s").string, MC.instance.fps
+ )
+ ), 36, MC.font.lineHeight, -1, true
+ )
+ it.context.pose().popMatrix()
+ }
+
+ if (TConfig.pingCount) {
+ it.context.pose().pushMatrix()
+ TConfig.pingCountHud.applyTransformations(it.context.pose())
+ val ping = MC.player?.let {
+ val entry: PlayerInfo? = MC.networkHandler?.getPlayerInfo(it.uuid)
+ entry?.latency ?: -1
+ } ?: -1
+ it.context.drawString(
+ MC.font, Component.literal(
+ String.format(
+ tr("firmament.config.hud.ping-count-hud.display", "Ping: %s ms").string, ping
+ )
+ ), 36, MC.font.lineHeight, -1, true
+ )
+
+ it.context.pose().popMatrix()
+ }
+ }
+}
diff --git a/src/main/kotlin/features/misc/LicenseViewer.kt b/src/main/kotlin/features/misc/LicenseViewer.kt
new file mode 100644
index 0000000..26bec80
--- /dev/null
+++ b/src/main/kotlin/features/misc/LicenseViewer.kt
@@ -0,0 +1,136 @@
+package moe.nea.firmament.features.misc
+
+import io.github.notenoughupdates.moulconfig.observer.ObservableList
+import io.github.notenoughupdates.moulconfig.xml.Bind
+import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.Transient
+import kotlinx.serialization.json.decodeFromStream
+import net.minecraft.network.chat.Component
+import moe.nea.firmament.Firmament
+import moe.nea.firmament.annotations.Subscribe
+import moe.nea.firmament.commands.thenExecute
+import moe.nea.firmament.events.CommandEvent
+import moe.nea.firmament.util.ErrorUtil
+import moe.nea.firmament.util.MC
+import moe.nea.firmament.util.MoulConfigUtils
+import moe.nea.firmament.util.ScreenUtil
+import moe.nea.firmament.util.tr
+
+object LicenseViewer {
+ @Serializable
+ data class Software(
+ val licenses: List<License> = listOf(),
+ val webPresence: String? = null,
+ val projectName: String,
+ val projectDescription: String? = null,
+ val developers: List<Developer> = listOf(),
+ ) {
+
+ @Bind
+ fun hasWebPresence() = webPresence != null
+
+ @Bind
+ fun webPresence() = Component.literal(webPresence ?: "<no web presence>")
+
+ @Bind
+ fun open() {
+ MC.openUrl(webPresence ?: return)
+ }
+
+ @Bind
+ fun projectName() = Component.literal(projectName)
+
+ @Bind
+ fun projectDescription() = Component.literal(projectDescription ?: "<no project description>")
+
+ @get:Bind("developers")
+ @Transient
+ val developersO = ObservableList(developers)
+
+ @get:Bind("licenses")
+ @Transient
+ val licenses0 = ObservableList(licenses)
+ }
+
+ @Serializable
+ data class Developer(
+ val name: String,
+ val webPresence: String? = null
+ ) {
+
+ @Bind("name")
+ fun nameT() = Component.literal(name)
+
+ @Bind
+ fun open() {
+ MC.openUrl(webPresence ?: return)
+ }
+
+ @Bind
+ fun hasWebPresence() = webPresence != null
+
+ @Bind
+ fun webPresence() = Component.literal(webPresence ?: "<no web presence>")
+ }
+
+ @Serializable
+ data class License(
+ val licenseName: String,
+ val licenseUrl: String? = null
+ ) {
+ @Bind("name")
+ fun nameG() = Component.literal(licenseName)
+
+ @Bind
+ fun open() {
+ MC.openUrl(licenseUrl ?: return)
+ }
+
+ @Bind
+ fun hasUrl() = licenseUrl != null
+
+ @Bind
+ fun url() = Component.literal(licenseUrl ?: "<no link to license text>")
+ }
+
+ data class LicenseList(
+ val softwares: List<Software>
+ ) {
+ @get:Bind("softwares")
+ val obs = ObservableList(softwares)
+ }
+
+ @OptIn(ExperimentalSerializationApi::class)
+ val licenses: LicenseList? = ErrorUtil.catch("Could not load licenses") {
+ Firmament.json.decodeFromStream<List<Software>?>(
+ javaClass.getResourceAsStream("/LICENSES-FIRMAMENT.json") ?: error("Could not find LICENSES-FIRMAMENT.json")
+ )?.let { LicenseList(it) }
+ }.orNull()
+
+ fun showLicenses() {
+ ErrorUtil.catch("Could not display licenses") {
+ ScreenUtil.setScreenLater(
+ MoulConfigUtils.loadScreen(
+ "license_viewer/index", licenses!!, null
+ )
+ )
+ }.or {
+ MC.sendChat(
+ tr(
+ "firmament.licenses.notfound",
+ "Could not load licenses. Please check the Firmament source code for information directly."
+ )
+ )
+ }
+ }
+
+ @Subscribe
+ fun onSubcommand(event: CommandEvent.SubCommand) {
+ event.subcommand("licenses") {
+ thenExecute {
+ showLicenses()
+ }
+ }
+ }
+}
diff --git a/src/main/kotlin/features/misc/ModAnnouncer.kt b/src/main/kotlin/features/misc/ModAnnouncer.kt
new file mode 100644
index 0000000..c2aa013
--- /dev/null
+++ b/src/main/kotlin/features/misc/ModAnnouncer.kt
@@ -0,0 +1,80 @@
+package moe.nea.firmament.features.misc
+
+import io.netty.buffer.ByteBuf
+import net.fabricmc.fabric.api.networking.v1.PacketByteBufs
+import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry
+import net.fabricmc.loader.api.FabricLoader
+import net.minecraft.network.codec.StreamCodec
+import net.minecraft.network.codec.ByteBufCodecs
+import net.minecraft.network.protocol.common.custom.CustomPacketPayload
+import net.minecraft.network.protocol.common.ServerboundCustomPayloadPacket
+import moe.nea.firmament.Firmament
+import moe.nea.firmament.annotations.Subscribe
+import moe.nea.firmament.events.JoinServerEvent
+
+/**
+ * This is a class that announces all mods to hypixel (or any other server you connect to).
+ *
+ * I don't mind cheat mods, i just think they should be honest about themselves.
+ *
+ * If you are a cheat mod (or think you could possibly be seen as one), you have two options:
+ *
+ * - risk getting your users banned from hypixel by exposing your modid + version number in a packet sent on first connect
+ * - hide yourself from this list by either a mixin, or (more easily) by specifying a tag in your fabric.mod.json:
+ *
+ * ```json
+ * {
+ * "schemaVersion": 1,
+ * "id": "my-cheat-mod",
+ * "custom": { "firmament:hide_from_modlist": true }
+ * }
+ * ```
+ */
+object ModAnnouncer {
+
+ data class ModEntry(
+ val modid: String,
+ val modVersion: String,
+ ) {
+ companion object {
+ val CODEC: StreamCodec<ByteBuf, ModEntry> = StreamCodec.composite(
+ ByteBufCodecs.STRING_UTF8, ModEntry::modid,
+ ByteBufCodecs.STRING_UTF8, ModEntry::modVersion,
+ ::ModEntry
+ )
+ }
+ }
+
+ data class ModPacket(
+ val mods: List<ModEntry>,
+ ) : CustomPacketPayload {
+ override fun type(): CustomPacketPayload.Type<out ModPacket> {
+ return ID
+ }
+
+ companion object {
+ val ID = CustomPacketPayload.Type<ModPacket>(Firmament.identifier("mod_list"))
+ val CODEC: StreamCodec<ByteBuf, ModPacket> = ModEntry.CODEC.apply(ByteBufCodecs.list())
+ .map(::ModPacket, ModPacket::mods)
+ }
+ }
+
+ @Subscribe
+ fun onServerJoin(event: JoinServerEvent) {
+ val packet = ModPacket(
+ FabricLoader.getInstance()
+ .allMods
+ .filter { !it.metadata.containsCustomValue("firmament:hide_from_modlist") }
+ .map { ModEntry(it.metadata.id, it.metadata.version.friendlyString) })
+ val pbb = PacketByteBufs.create()
+ ModPacket.CODEC.encode(pbb, packet)
+ if (pbb.writerIndex() > ServerboundCustomPayloadPacket.MAX_PAYLOAD_SIZE)
+ return
+
+ event.networkHandler.send(event.packetSender.createPacket(packet))
+ }
+
+ init {
+ PayloadTypeRegistry.playC2S().register(ModPacket.ID, ModPacket.CODEC)
+ }
+}