diff options
author | Linnea Gräf <nea@nea.moe> | 2025-06-23 13:34:02 +0200 |
---|---|---|
committer | Linnea Gräf <nea@nea.moe> | 2025-06-23 13:34:02 +0200 |
commit | 051668fe9ac8d02fd9546e588d4255ee5e120f40 (patch) | |
tree | c36e1415e78c37c71b0abaab3099349d8650d9dd /src | |
parent | 72b8d8c8a37cd4396de44f0bcd2a9e8bd4b073b9 (diff) | |
download | Firmament-051668fe9ac8d02fd9546e588d4255ee5e120f40.tar.gz Firmament-051668fe9ac8d02fd9546e588d4255ee5e120f40.tar.bz2 Firmament-051668fe9ac8d02fd9546e588d4255ee5e120f40.zip |
feat: Add dev capes
Diffstat (limited to 'src')
13 files changed, 347 insertions, 3 deletions
diff --git a/src/main/java/moe/nea/firmament/mixins/feature/devcosmetics/CustomCapeFeatureRenderer.java b/src/main/java/moe/nea/firmament/mixins/feature/devcosmetics/CustomCapeFeatureRenderer.java new file mode 100644 index 0000000..398400f --- /dev/null +++ b/src/main/java/moe/nea/firmament/mixins/feature/devcosmetics/CustomCapeFeatureRenderer.java @@ -0,0 +1,44 @@ +package moe.nea.firmament.mixins.feature.devcosmetics; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Local; +import kotlin.Unit; +import moe.nea.firmament.features.misc.CustomCapes; +import net.minecraft.client.render.RenderLayer; +import net.minecraft.client.render.VertexConsumer; +import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.render.entity.feature.CapeFeatureRenderer; +import net.minecraft.client.render.entity.feature.FeatureRenderer; +import net.minecraft.client.render.entity.feature.FeatureRendererContext; +import net.minecraft.client.render.entity.model.BipedEntityModel; +import net.minecraft.client.render.entity.model.PlayerEntityModel; +import net.minecraft.client.render.entity.state.PlayerEntityRenderState; +import net.minecraft.client.util.SkinTextures; +import net.minecraft.client.util.math.MatrixStack; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(CapeFeatureRenderer.class) +public abstract class CustomCapeFeatureRenderer extends FeatureRenderer<PlayerEntityRenderState, PlayerEntityModel> { + public CustomCapeFeatureRenderer(FeatureRendererContext<PlayerEntityRenderState, PlayerEntityModel> context) { + super(context); + } + + @WrapOperation( + method = "render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/client/render/entity/state/PlayerEntityRenderState;FF)V", + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/entity/model/BipedEntityModel;render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumer;II)V") + ) + private void onRender(BipedEntityModel instance, MatrixStack matrixStack, VertexConsumer vertexConsumer, int light, int overlay, Operation<Void> original, @Local PlayerEntityRenderState playerEntityRenderState, @Local SkinTextures skinTextures, @Local VertexConsumerProvider vertexConsumerProvider) { + // TODO: load special cape from playerEntityRenderState + CustomCapes.render( + playerEntityRenderState, + vertexConsumer, + RenderLayer.getEntitySolid(skinTextures.capeTexture()), + vertexConsumerProvider, + updatedConsumer -> { + original.call(instance, matrixStack, updatedConsumer, light, overlay); + return Unit.INSTANCE; + }); + } +} diff --git a/src/main/java/moe/nea/firmament/mixins/feature/devcosmetics/CustomCapeStorage.java b/src/main/java/moe/nea/firmament/mixins/feature/devcosmetics/CustomCapeStorage.java new file mode 100644 index 0000000..428d7ec --- /dev/null +++ b/src/main/java/moe/nea/firmament/mixins/feature/devcosmetics/CustomCapeStorage.java @@ -0,0 +1,23 @@ +package moe.nea.firmament.mixins.feature.devcosmetics; + +import moe.nea.firmament.features.misc.CustomCapes; +import net.minecraft.client.render.entity.state.PlayerEntityRenderState; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; + +@Mixin(PlayerEntityRenderState.class) +public class CustomCapeStorage implements CustomCapes.CapeStorage { + @Unique + CustomCapes.CustomCape customCape; + + @Override + public CustomCapes.@Nullable CustomCape getCape_firmament() { + return customCape; + } + + @Override + public void setCape_firmament(CustomCapes.@Nullable CustomCape customCape) { + this.customCape = customCape; + } +} diff --git a/src/main/java/moe/nea/firmament/mixins/feature/devcosmetics/SaveCapeToPlayerEntityRenderState.java b/src/main/java/moe/nea/firmament/mixins/feature/devcosmetics/SaveCapeToPlayerEntityRenderState.java new file mode 100644 index 0000000..72bb79a --- /dev/null +++ b/src/main/java/moe/nea/firmament/mixins/feature/devcosmetics/SaveCapeToPlayerEntityRenderState.java @@ -0,0 +1,19 @@ +package moe.nea.firmament.mixins.feature.devcosmetics; + +import moe.nea.firmament.features.misc.CustomCapes; +import net.minecraft.client.network.AbstractClientPlayerEntity; +import net.minecraft.client.render.entity.PlayerEntityRenderer; +import net.minecraft.client.render.entity.state.PlayerEntityRenderState; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(PlayerEntityRenderer.class) +public class SaveCapeToPlayerEntityRenderState { + @Inject(method = "updateRenderState(Lnet/minecraft/client/network/AbstractClientPlayerEntity;Lnet/minecraft/client/render/entity/state/PlayerEntityRenderState;F)V", + at = @At("TAIL")) + private void addCustomCape(AbstractClientPlayerEntity abstractClientPlayerEntity, PlayerEntityRenderState playerEntityRenderState, float f, CallbackInfo ci) { +CustomCapes.addCapeData(abstractClientPlayerEntity, playerEntityRenderState); + } +} diff --git a/src/main/kotlin/features/debug/itemeditor/ExportRecipe.kt b/src/main/kotlin/features/debug/itemeditor/ExportRecipe.kt index d7f4620..4d43f6e 100644 --- a/src/main/kotlin/features/debug/itemeditor/ExportRecipe.kt +++ b/src/main/kotlin/features/debug/itemeditor/ExportRecipe.kt @@ -5,7 +5,6 @@ import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive import net.minecraft.client.network.AbstractClientPlayerEntity -import net.minecraft.client.network.ClientPlayerEntity import net.minecraft.entity.decoration.ArmorStandEntity import moe.nea.firmament.Firmament import moe.nea.firmament.annotations.Subscribe @@ -64,7 +63,7 @@ object ExportRecipe { ?: "" val reply = waitForTextInput("$guessName (NPC)", "Export stub") val id = generateName(reply) - ItemExporter.exportStub(id, reply) { + ItemExporter.exportStub(id, "§9$reply") { val playerEntity = entity as? AbstractClientPlayerEntity val textureUrl = playerEntity?.skinTextures?.textureUrl if (textureUrl != null) @@ -117,7 +116,7 @@ object ExportRecipe { val shopId = SkyblockId(title.uppercase().replace(" ", "_") + "_NPC") if (!ItemExporter.isExported(shopId)) { // TODO: export location + skin of last clicked npc - ItemExporter.exportStub(shopId, "$title (NPC)") + ItemExporter.exportStub(shopId, "§9$title (NPC)") } for (index in (9..9 * 5)) { val item = event.screen.getSlotByIndex(index, false)?.stack ?: continue diff --git a/src/main/kotlin/features/misc/CustomCapes.kt b/src/main/kotlin/features/misc/CustomCapes.kt new file mode 100644 index 0000000..5e4764e --- /dev/null +++ b/src/main/kotlin/features/misc/CustomCapes.kt @@ -0,0 +1,165 @@ +package moe.nea.firmament.features.misc + +import com.mojang.blaze3d.systems.RenderSystem +import java.util.OptionalDouble +import java.util.OptionalInt +import util.render.CustomRenderPipelines +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds +import net.minecraft.client.network.AbstractClientPlayerEntity +import net.minecraft.client.render.BufferBuilder +import net.minecraft.client.render.RenderLayer +import net.minecraft.client.render.VertexConsumer +import net.minecraft.client.render.VertexConsumerProvider +import net.minecraft.client.render.entity.state.PlayerEntityRenderState +import net.minecraft.client.util.BufferAllocator +import net.minecraft.util.Identifier +import moe.nea.firmament.Firmament +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.TimeMark + +object CustomCapes { + interface CustomCapeRenderer { + fun replaceRender( + renderLayer: RenderLayer, + vertexConsumerProvider: VertexConsumerProvider, + model: (VertexConsumer) -> Unit + ) + } + + data class TexturedCapeRenderer( + val location: Identifier + ) : CustomCapeRenderer { + override fun replaceRender( + renderLayer: RenderLayer, + vertexConsumerProvider: VertexConsumerProvider, + model: (VertexConsumer) -> Unit + ) { + model(vertexConsumerProvider.getBuffer(RenderLayer.getEntitySolid(location))) + } + } + + data class ParallaxedHighlightCapeRenderer( + val template: Identifier, + val background: Identifier, + val overlay: Identifier, + val animationSpeed: Duration, + ) : CustomCapeRenderer { + override fun replaceRender( + renderLayer: RenderLayer, + vertexConsumerProvider: VertexConsumerProvider, + model: (VertexConsumer) -> Unit + ) { + BufferAllocator(2048).use { allocator -> + val bufferBuilder = BufferBuilder(allocator, renderLayer.drawMode, renderLayer.vertexFormat) + model(bufferBuilder) + bufferBuilder.end().use { buffer -> + val commandEncoder = RenderSystem.getDevice().createCommandEncoder() + val vertexBuffer = renderLayer.vertexFormat.uploadImmediateVertexBuffer(buffer.buffer) + val indexBufferConstructor = RenderSystem.getSequentialBuffer(renderLayer.drawMode) + val indexBuffer = indexBufferConstructor.getIndexBuffer(buffer.drawParameters.indexCount) + val templateTexture = MC.textureManager.getTexture(template) + val backgroundTexture = MC.textureManager.getTexture(background) + val foregroundTexture = MC.textureManager.getTexture(overlay) + commandEncoder.createRenderPass( + MC.instance.framebuffer.colorAttachment, + OptionalInt.empty(), + MC.instance.framebuffer.depthAttachment, + OptionalDouble.empty(), + ).use { renderPass -> + // TODO: account for lighting + renderPass.setPipeline(CustomRenderPipelines.PARALLAX_CAPE_SHADER) + renderPass.bindSampler("Sampler0", templateTexture.glTexture) + renderPass.bindSampler("Sampler1", backgroundTexture.glTexture) + renderPass.bindSampler("Sampler3", foregroundTexture.glTexture) + val animationValue = (startTime.passedTime() / animationSpeed).mod(1F) + renderPass.setUniform("Animation", animationValue.toFloat()) + renderPass.setIndexBuffer(indexBuffer, indexBufferConstructor.indexType) + renderPass.setVertexBuffer(0, vertexBuffer) + renderPass.drawIndexed(0, buffer.drawParameters.indexCount) + } + } + } + } + } + + interface CapeStorage { + companion object { + @JvmStatic + fun cast(playerEntityRenderState: PlayerEntityRenderState) = + 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 + ) + ), +// FURFSKY( +// "FurfSky", +// TexturedCapeRenderer(Firmament.identifier("textures/cape/fsr_static.png")) +// ), + FIRMAMENT_STATIC( + "Firmament", + TexturedCapeRenderer(Firmament.identifier("textures/cape/firm_static.png")) + ) + ; + + val cape = CustomCape(name, label, render) + } + + val byId = AllCapes.entries.associateBy { it.cape.id } + val byUuid = listOf( + Devs.nea to AllCapes.FIRMAMENT_ANIMATED, + Devs.kath to AllCapes.FIRMAMENT_STATIC, + Devs.jani to AllCapes.FIRMAMENT_ANIMATED, + ).flatMap { (dev, cape) -> dev.uuids.map { it to cape.cape } }.toMap() + + @JvmStatic + fun render( + playerEntityRenderState: PlayerEntityRenderState, + vertexConsumer: VertexConsumer, + renderLayer: RenderLayer, + vertexConsumerProvider: VertexConsumerProvider, + model: (VertexConsumer) -> Unit + ) { + val capeStorage = CapeStorage.cast(playerEntityRenderState) + val firmCape = capeStorage.cape_firmament + if (firmCape != null) { + firmCape.render.replaceRender(renderLayer, vertexConsumerProvider, model) + } else { + model(vertexConsumer) + } + } + + @JvmStatic + fun addCapeData( + player: AbstractClientPlayerEntity, + playerEntityRenderState: PlayerEntityRenderState + ) { + val cape = byUuid[player.uuid] + val capeStorage = CapeStorage.cast(playerEntityRenderState) + if (cape == null) { + capeStorage.cape_firmament = null + } else { + capeStorage.cape_firmament = cape + playerEntityRenderState.capeVisible = 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..7a5da2b --- /dev/null +++ b/src/main/kotlin/features/misc/Devs.kt @@ -0,0 +1,16 @@ +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") +} diff --git a/src/main/kotlin/util/render/CustomRenderLayers.kt b/src/main/kotlin/util/render/CustomRenderLayers.kt index be0bbd7..3d9e598 100644 --- a/src/main/kotlin/util/render/CustomRenderLayers.kt +++ b/src/main/kotlin/util/render/CustomRenderLayers.kt @@ -49,6 +49,15 @@ object CustomRenderPipelines { .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.FLOAT) + .build() } object CustomRenderLayers { diff --git a/src/main/resources/assets/firmament/shaders/cape/parallax.fsh b/src/main/resources/assets/firmament/shaders/cape/parallax.fsh new file mode 100644 index 0000000..bc9a440 --- /dev/null +++ b/src/main/resources/assets/firmament/shaders/cape/parallax.fsh @@ -0,0 +1,53 @@ +#version 150 + +#moj_import <minecraft:fog.glsl> +#define M_PI 3.1415926535897932384626433832795 +#define M_TAU (2.0 * M_PI) +uniform sampler2D Sampler0; +uniform sampler2D Sampler1; +uniform sampler2D Sampler3; + +uniform vec4 ColorModulator; +uniform float FogStart; +uniform float FogEnd; +uniform vec4 FogColor; +uniform float Animation; + +in float vertexDistance; +in vec4 vertexColor; +in vec4 lightMapColor; +in vec4 overlayColor; +in vec2 texCoord0; + +out vec4 fragColor; + +float highlightDistance(vec2 coord, vec2 direction, float time) { + vec2 dir = normalize(direction); + float projection = dot(coord, dir); + float animationTime = sin(projection + time * 13 * M_TAU); + if (animationTime < 0.997) { + return 0.0; + } + return animationTime; +} + +void main() { + vec4 color = texture(Sampler0, texCoord0); + if (color.g > 0.99) { + // TODO: maybe this speed in each direction should be a uniform + color = texture(Sampler1, texCoord0 + Animation * vec2(3.0, -2.0)); + } + + vec4 highlightColor = texture(Sampler3, texCoord0); + if (highlightColor.a > 0.5) { + color = highlightColor; + float animationHighlight = highlightDistance(texCoord0, vec2(-12.0, 2.0), Animation); + color.rgb += (animationHighlight); + } + #ifdef ALPHA_CUTOUT + if (color.a < ALPHA_CUTOUT) { + discard; + } + #endif + fragColor = linear_fog(color, vertexDistance, FogStart, FogEnd, FogColor); +} diff --git a/src/main/resources/assets/firmament/textures/cape/REUSE.toml b/src/main/resources/assets/firmament/textures/cape/REUSE.toml new file mode 100644 index 0000000..7cb21f2 --- /dev/null +++ b/src/main/resources/assets/firmament/textures/cape/REUSE.toml @@ -0,0 +1,16 @@ +#SPDX-FileCopyrightText: 2025 Linnea Gräf <nea@nea.moe> +# +#SPDX-License-Identifier: CC0-1.0 +version = 1 + +[[annotations]] +path = ["firmament_star.png", "parallax_background.png", "parallax_template.png"] +SPDX-License-Identifier = "CC-BY-4.0" +SPDX-FileCopyrightText = ["ic22487", "Linnea Gräf"] + +[[annotations]] +path = ["firm_static.png"] +SPDX-License-Identifier = "CC-BY-4.0" +SPDX-FileCopyrightText = ["ic22487", "kathund"] + + diff --git a/src/main/resources/assets/firmament/textures/cape/firm_static.png b/src/main/resources/assets/firmament/textures/cape/firm_static.png Binary files differnew file mode 100644 index 0000000..b01511c --- /dev/null +++ b/src/main/resources/assets/firmament/textures/cape/firm_static.png diff --git a/src/main/resources/assets/firmament/textures/cape/firmament_star.png b/src/main/resources/assets/firmament/textures/cape/firmament_star.png Binary files differnew file mode 100644 index 0000000..520d309 --- /dev/null +++ b/src/main/resources/assets/firmament/textures/cape/firmament_star.png diff --git a/src/main/resources/assets/firmament/textures/cape/parallax_background.png b/src/main/resources/assets/firmament/textures/cape/parallax_background.png Binary files differnew file mode 100644 index 0000000..05ef0fa --- /dev/null +++ b/src/main/resources/assets/firmament/textures/cape/parallax_background.png diff --git a/src/main/resources/assets/firmament/textures/cape/parallax_template.png b/src/main/resources/assets/firmament/textures/cape/parallax_template.png Binary files differnew file mode 100644 index 0000000..ada76eb --- /dev/null +++ b/src/main/resources/assets/firmament/textures/cape/parallax_template.png |