aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinnea Gräf <nea@nea.moe>2025-02-02 00:08:18 +0100
committerLinnea Gräf <nea@nea.moe>2025-02-02 00:08:18 +0100
commit89bceb6735a1e934287c3e01636a203758b085c8 (patch)
tree0766b8d4c10510bdd09f69fc1b50f7a4c1849372
parentc2aefe09ab9bb8fec234ec10cbfc127dd6f7e4d2 (diff)
downloadFirmament-89bceb6735a1e934287c3e01636a203758b085c8.tar.gz
Firmament-89bceb6735a1e934287c3e01636a203758b085c8.tar.bz2
Firmament-89bceb6735a1e934287c3e01636a203758b085c8.zip
feat: Highlight century cake slice players
-rw-r--r--build.gradle.kts76
-rw-r--r--src/main/java/moe/nea/firmament/mixins/render/entitytints/ChangeColorOfLivingEntities.java62
-rw-r--r--src/main/java/moe/nea/firmament/mixins/render/entitytints/EntityRenderStateTint.java55
-rw-r--r--src/main/java/moe/nea/firmament/mixins/render/entitytints/InjectIntoRenderState.java30
-rw-r--r--src/main/java/moe/nea/firmament/mixins/render/entitytints/ReplaceOverlayTexture.java24
-rw-r--r--src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableEquipmentRenderer.java34
-rw-r--r--src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableHeadFeatureRenderer.java25
-rw-r--r--src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableItemRenderer.java25
-rw-r--r--src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableSkullBlockEntityRenderer.java25
-rw-r--r--src/main/kotlin/commands/rome.kt1
-rw-r--r--src/main/kotlin/events/EntityRenderTintEvent.kt66
-rw-r--r--src/main/kotlin/features/debug/PowerUserTools.kt15
-rw-r--r--src/main/kotlin/features/events/anniversity/CenturyRaffleFeatures.kt63
-rw-r--r--src/main/kotlin/util/MC.kt4
-rw-r--r--src/main/kotlin/util/render/TintedOverlayTexture.kt44
-rw-r--r--src/main/kotlin/util/skyblock/SkyBlockItems.kt5
-rw-r--r--src/main/resources/firmament.accesswidener5
-rw-r--r--translations/en_us.json3
18 files changed, 559 insertions, 3 deletions
diff --git a/build.gradle.kts b/build.gradle.kts
index 1a31f16..40eb5d8 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -6,15 +6,21 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
+import com.google.common.hash.Hashing
import com.google.devtools.ksp.gradle.KspTaskJvm
import com.google.gson.Gson
import com.google.gson.JsonObject
import moe.nea.licenseextractificator.LicenseDiscoveryTask
import moe.nea.mcautotranslations.gradle.CollectTranslations
import net.fabricmc.loom.LoomGradleExtension
+import org.apache.tools.ant.taskdefs.condition.Os
+import org.gradle.platform.OperatingSystem
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.plugin.SubpluginOption
+import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsPlugin.Companion.kotlinNodeJsEnvSpec
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+import java.nio.charset.StandardCharsets
+import java.util.Base64
plugins {
java
@@ -337,9 +343,9 @@ loom {
named("client") {
property("devauth.enabled", "true")
vmArg("-ea")
- vmArg("-XX:+AllowEnhancedClassRedefinition")
- vmArg("-XX:HotswapAgent=external")
- vmArg("-javaagent:${hotswap.resolve().single().absolutePath}")
+// vmArg("-XX:+AllowEnhancedClassRedefinition")
+// vmArg("-XX:HotswapAgent=external")
+// vmArg("-javaagent:${hotswap.resolve().single().absolutePath}")
}
}
}
@@ -463,6 +469,70 @@ tasks.create("printAllLicenses", LicenseDiscoveryTask::class.java, licensing).ap
}
outputs.upToDateWhen { false }
}
+fun patchRenderDoc(
+ javaLauncher: JavaLauncher,
+): JavaLauncher {
+ val wrappedJavaExecutable = javaLauncher.executablePath.asFile.absolutePath
+ require("\"" !in wrappedJavaExecutable)
+ val hashBytes = Hashing.sha256().hashString(wrappedJavaExecutable, StandardCharsets.UTF_8)
+ val hash = Base64.getUrlEncoder().encodeToString(hashBytes.asBytes())
+ .replace("=", "")
+ val wrapperJavaRoot = rootProject.layout.buildDirectory
+ .dir("binaries/renderdoc-wrapped-java/$hash/")
+ .get()
+ val isWindows = Os.isFamily(Os.FAMILY_WINDOWS)
+ val wrapperJavaExe =
+ if (isWindows) wrapperJavaRoot.file("java.cmd")
+ else wrapperJavaRoot.file("java")
+ return object : JavaLauncher {
+ override fun getMetadata(): JavaInstallationMetadata {
+ return object : JavaInstallationMetadata by javaLauncher.metadata {
+ override fun isCurrentJvm(): Boolean {
+ return false
+ }
+ }
+ }
+
+ override fun getExecutablePath(): RegularFile {
+ val fileF = wrapperJavaExe.asFile
+ if (!fileF.exists()) {
+ fileF.parentFile.mkdirs()
+ if (isWindows) {
+ fileF.writeText("""
+ setlocal enableextensions
+ start "" renderdoccmd.exe capture --opt-hook-children --wait-for-exit --working-dir . "$wrappedJavaExecutable" %*
+ endlocal
+ """.trimIndent())
+ } else {
+ fileF.writeText("""
+ #!/usr/bin/env bash
+ exec renderdoccmd capture --opt-hook-children --wait-for-exit --working-dir . "$wrappedJavaExecutable" "$@"
+ """.trimIndent())
+ fileF.setExecutable(true)
+ }
+ }
+ return wrapperJavaExe
+ }
+ }
+}
+tasks.runClient {
+ javaLauncher.set(javaToolchains.launcherFor(java.toolchain).map { patchRenderDoc(it) })
+}
+//tasks.register<Exec>("runRenderDoc") {
+// val runClient = tasks.runClient.get()
+// commandLine(
+// "renderdoc",
+// "capture",
+// "--opt-hook-children",
+// "--wait-for-exit",
+// "--working-dir",
+// runClient.workingDir,
+// runClient.javaLauncher.get().executablePath.asFile.absoluteFile,
+// )
+// args(runClient.allJvmArgs)
+// args()
+//
+//}
tasks.withType<AbstractArchiveTask>().configureEach {
isPreserveFileTimestamps = false
diff --git a/src/main/java/moe/nea/firmament/mixins/render/entitytints/ChangeColorOfLivingEntities.java b/src/main/java/moe/nea/firmament/mixins/render/entitytints/ChangeColorOfLivingEntities.java
new file mode 100644
index 0000000..2b96e5c
--- /dev/null
+++ b/src/main/java/moe/nea/firmament/mixins/render/entitytints/ChangeColorOfLivingEntities.java
@@ -0,0 +1,62 @@
+package moe.nea.firmament.mixins.render.entitytints;
+
+import com.llamalad7.mixinextras.injector.ModifyReturnValue;
+import com.llamalad7.mixinextras.sugar.Local;
+import moe.nea.firmament.events.EntityRenderTintEvent;
+import net.minecraft.client.render.VertexConsumerProvider;
+import net.minecraft.client.render.entity.LivingEntityRenderer;
+import net.minecraft.client.render.entity.model.EntityModel;
+import net.minecraft.client.render.entity.state.LivingEntityRenderState;
+import net.minecraft.client.util.math.MatrixStack;
+import net.minecraft.entity.LivingEntity;
+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.ModifyArg;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+/**
+ * Applies various rendering modifications from {@link EntityRenderTintEvent}
+ */
+@Mixin(LivingEntityRenderer.class)
+public class ChangeColorOfLivingEntities<T extends LivingEntity, S extends LivingEntityRenderState, M extends EntityModel<? super S>> {
+ @ModifyReturnValue(method = "getMixColor", at = @At("RETURN"))
+ private int changeColor(int original, @Local(argsOnly = true) S state) {
+ var tintState = EntityRenderTintEvent.HasTintRenderState.cast(state);
+ if (tintState.getHasTintOverride_firmament())
+ return tintState.getTint_firmament();
+ return original;
+ }
+
+ @ModifyArg(
+ method = "getOverlay",
+ at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/OverlayTexture;getU(F)I"),
+ allow = 1
+ )
+ private static float modifyLightOverlay(float originalWhiteOffset, @Local(argsOnly = true) LivingEntityRenderState state) {
+ var tintState = EntityRenderTintEvent.HasTintRenderState.cast(state);
+ if (tintState.getHasTintOverride_firmament() || tintState.getOverlayTexture_firmament() != null) {
+ return 1F; // TODO: add interpolation percentage to render state extension
+ }
+ return originalWhiteOffset;
+ }
+
+ @Inject(method = "render(Lnet/minecraft/client/render/entity/state/LivingEntityRenderState;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;I)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/util/math/MatrixStack;pop()V"))
+ private void afterRender(S livingEntityRenderState, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int i, CallbackInfo ci) {
+ var tintState = EntityRenderTintEvent.HasTintRenderState.cast(livingEntityRenderState);
+ var overlayTexture = tintState.getOverlayTexture_firmament();
+ if (overlayTexture != null && vertexConsumerProvider instanceof VertexConsumerProvider.Immediate imm) {
+ imm.drawCurrentLayer();
+ }
+ EntityRenderTintEvent.overlayOverride = null;
+ }
+
+ @Inject(method = "render(Lnet/minecraft/client/render/entity/state/LivingEntityRenderState;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;I)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/util/math/MatrixStack;push()V"))
+ private void beforeRender(S livingEntityRenderState, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int i, CallbackInfo ci) {
+ var tintState = EntityRenderTintEvent.HasTintRenderState.cast(livingEntityRenderState);
+ var overlayTexture = tintState.getOverlayTexture_firmament();
+ if (overlayTexture != null) {
+ EntityRenderTintEvent.overlayOverride = overlayTexture;
+ }
+ }
+}
diff --git a/src/main/java/moe/nea/firmament/mixins/render/entitytints/EntityRenderStateTint.java b/src/main/java/moe/nea/firmament/mixins/render/entitytints/EntityRenderStateTint.java
new file mode 100644
index 0000000..1019027
--- /dev/null
+++ b/src/main/java/moe/nea/firmament/mixins/render/entitytints/EntityRenderStateTint.java
@@ -0,0 +1,55 @@
+package moe.nea.firmament.mixins.render.entitytints;
+
+import moe.nea.firmament.events.EntityRenderTintEvent;
+import moe.nea.firmament.util.render.TintedOverlayTexture;
+import net.minecraft.client.render.entity.state.EntityRenderState;
+import org.jetbrains.annotations.Nullable;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Unique;
+
+@Mixin(EntityRenderState.class)
+public class EntityRenderStateTint implements EntityRenderTintEvent.HasTintRenderState {
+ @Unique
+ int tint = -1;
+ @Unique
+ TintedOverlayTexture overlayTexture;
+ @Unique
+ boolean hasTintOverride = false;
+
+ @Override
+ public int getTint_firmament() {
+ return tint;
+ }
+
+ @Override
+ public void setTint_firmament(int i) {
+ tint = i;
+ hasTintOverride = true;
+ }
+
+ @Override
+ public boolean getHasTintOverride_firmament() {
+ return hasTintOverride;
+ }
+
+ @Override
+ public void setHasTintOverride_firmament(boolean b) {
+ hasTintOverride = b;
+ }
+
+ @Override
+ public void reset_firmament() {
+ hasTintOverride = false;
+ overlayTexture = null;
+ }
+
+ @Override
+ public @Nullable TintedOverlayTexture getOverlayTexture_firmament() {
+ return overlayTexture;
+ }
+
+ @Override
+ public void setOverlayTexture_firmament(@Nullable TintedOverlayTexture tintedOverlayTexture) {
+ this.overlayTexture = tintedOverlayTexture;
+ }
+}
diff --git a/src/main/java/moe/nea/firmament/mixins/render/entitytints/InjectIntoRenderState.java b/src/main/java/moe/nea/firmament/mixins/render/entitytints/InjectIntoRenderState.java
new file mode 100644
index 0000000..7938340
--- /dev/null
+++ b/src/main/java/moe/nea/firmament/mixins/render/entitytints/InjectIntoRenderState.java
@@ -0,0 +1,30 @@
+package moe.nea.firmament.mixins.render.entitytints;
+
+import moe.nea.firmament.events.EntityRenderTintEvent;
+import net.minecraft.client.render.entity.EntityRenderer;
+import net.minecraft.client.render.entity.state.EntityRenderState;
+import net.minecraft.entity.Entity;
+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;
+
+/**
+ * Dispatches {@link EntityRenderTintEvent} to collect additional render state used by {@link ChangeColorOfLivingEntities}
+ */
+@Mixin(EntityRenderer.class)
+public class InjectIntoRenderState<T extends Entity, S extends EntityRenderState> {
+
+ @Inject(
+ method = "updateRenderState",
+ at = @At("RETURN"))
+ private void onUpdateRenderState(T entity, S state, float tickDelta, CallbackInfo ci) {
+ var renderState = EntityRenderTintEvent.HasTintRenderState.cast(state);
+ renderState.reset_firmament();
+ var tintEvent = new EntityRenderTintEvent(
+ entity,
+ renderState
+ );
+ EntityRenderTintEvent.Companion.publish(tintEvent);
+ }
+}
diff --git a/src/main/java/moe/nea/firmament/mixins/render/entitytints/ReplaceOverlayTexture.java b/src/main/java/moe/nea/firmament/mixins/render/entitytints/ReplaceOverlayTexture.java
new file mode 100644
index 0000000..61e5c65
--- /dev/null
+++ b/src/main/java/moe/nea/firmament/mixins/render/entitytints/ReplaceOverlayTexture.java
@@ -0,0 +1,24 @@
+package moe.nea.firmament.mixins.render.entitytints;
+
+import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
+import moe.nea.firmament.events.EntityRenderTintEvent;
+import net.minecraft.client.render.OverlayTexture;
+import net.minecraft.client.render.RenderLayer;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+
+/**
+ * Replaces the overlay texture used by rendering with the override specified in {@link EntityRenderTintEvent#overlayOverride}
+ */
+@Mixin(RenderLayer.Overlay.class)
+public class ReplaceOverlayTexture {
+ @ModifyExpressionValue(
+ method = {"method_23555", "method_23556"},
+ expect = 2,
+ at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/GameRenderer;getOverlayTexture()Lnet/minecraft/client/render/OverlayTexture;"))
+ private static OverlayTexture replaceOverlayTexture(OverlayTexture original) {
+ if (EntityRenderTintEvent.overlayOverride != null)
+ return EntityRenderTintEvent.overlayOverride;
+ return original;
+ }
+}
diff --git a/src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableEquipmentRenderer.java b/src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableEquipmentRenderer.java
new file mode 100644
index 0000000..d9c174c
--- /dev/null
+++ b/src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableEquipmentRenderer.java
@@ -0,0 +1,34 @@
+package moe.nea.firmament.mixins.render.entitytints;
+
+import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
+import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
+import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
+import moe.nea.firmament.events.EntityRenderTintEvent;
+import net.minecraft.client.render.OverlayTexture;
+import net.minecraft.client.render.RenderLayer;
+import net.minecraft.client.render.entity.equipment.EquipmentRenderer;
+import net.minecraft.util.Identifier;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+
+/**
+ * Patch to make {@link EquipmentRenderer} use a {@link RenderLayer} that allows uses Minecraft's overlay texture, if a {@link EntityRenderTintEvent#overlayOverride} is specified.
+ */
+@Mixin(EquipmentRenderer.class)
+public class UseOverlayableEquipmentRenderer {
+ @WrapOperation(method = "render(Lnet/minecraft/client/render/entity/equipment/EquipmentModel$LayerType;Lnet/minecraft/registry/RegistryKey;Lnet/minecraft/client/model/Model;Lnet/minecraft/item/ItemStack;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/util/Identifier;)V",
+ at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/RenderLayer;getArmorCutoutNoCull(Lnet/minecraft/util/Identifier;)Lnet/minecraft/client/render/RenderLayer;"))
+ private RenderLayer replace(Identifier texture, Operation<RenderLayer> original) {
+ if (EntityRenderTintEvent.overlayOverride != null)
+ return RenderLayer.getEntityTranslucent(texture);
+ return original.call(texture);
+ }
+
+ @ModifyExpressionValue(method = "render(Lnet/minecraft/client/render/entity/equipment/EquipmentModel$LayerType;Lnet/minecraft/registry/RegistryKey;Lnet/minecraft/client/model/Model;Lnet/minecraft/item/ItemStack;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/util/Identifier;)V",
+ at = @At(value = "FIELD", target = "Lnet/minecraft/client/render/OverlayTexture;DEFAULT_UV:I"))
+ private int replaceUvIndex(int original) {
+ if (EntityRenderTintEvent.overlayOverride != null)
+ return OverlayTexture.packUv(15, 10); // TODO: store this info in a global alongside overlayOverride
+ return original;
+ }
+}
diff --git a/src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableHeadFeatureRenderer.java b/src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableHeadFeatureRenderer.java
new file mode 100644
index 0000000..07bc5cf
--- /dev/null
+++ b/src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableHeadFeatureRenderer.java
@@ -0,0 +1,25 @@
+package moe.nea.firmament.mixins.render.entitytints;
+
+import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
+import moe.nea.firmament.events.EntityRenderTintEvent;
+import net.minecraft.client.render.OverlayTexture;
+import net.minecraft.client.render.RenderLayer;
+import net.minecraft.client.render.entity.feature.HeadFeatureRenderer;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+
+/**
+ * Patch to make {@link HeadFeatureRenderer} use a {@link RenderLayer} that allows uses Minecraft's overlay texture, if a {@link EntityRenderTintEvent#overlayOverride} is specified.
+ * @see UseOverlayableItemRenderer
+ */
+@Mixin(HeadFeatureRenderer.class)
+public class UseOverlayableHeadFeatureRenderer {
+
+ @ModifyExpressionValue(method = "render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/client/render/entity/state/LivingEntityRenderState;FF)V",
+ at = @At(value = "FIELD", target = "Lnet/minecraft/client/render/OverlayTexture;DEFAULT_UV:I"))
+ private int replaceUvIndex(int original) {
+ if (EntityRenderTintEvent.overlayOverride != null)
+ return OverlayTexture.packUv(15, 10); // TODO: store this info in a global alongside overlayOverride
+ return original;
+ }
+}
diff --git a/src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableItemRenderer.java b/src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableItemRenderer.java
new file mode 100644
index 0000000..620ab2c
--- /dev/null
+++ b/src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableItemRenderer.java
@@ -0,0 +1,25 @@
+package moe.nea.firmament.mixins.render.entitytints;
+
+import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
+import moe.nea.firmament.events.EntityRenderTintEvent;
+import net.minecraft.client.render.OverlayTexture;
+import net.minecraft.client.render.RenderLayer;
+import net.minecraft.client.render.RenderPhase;
+import net.minecraft.client.render.item.ItemRenderState;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+
+/**
+ * Patch to make {@link ItemRenderState} use a {@link RenderLayer} that allows uses Minecraft's overlay texture.
+ *
+ * @see UseOverlayableHeadFeatureRenderer
+ */
+@Mixin(ItemRenderState.LayerRenderState.class)
+public class UseOverlayableItemRenderer {
+ @ModifyExpressionValue(method = "render", at = @At(value = "FIELD", target = "Lnet/minecraft/client/render/item/ItemRenderState$LayerRenderState;renderLayer:Lnet/minecraft/client/render/RenderLayer;"))
+ private RenderLayer replace(RenderLayer original) {
+ if (EntityRenderTintEvent.overlayOverride != null && original instanceof RenderLayer.MultiPhase multiPhase && multiPhase.phases.texture instanceof RenderPhase.Texture texture && texture.getId().isPresent())
+ return RenderLayer.getEntityTranslucent(texture.getId().get());
+ return original;
+ }
+}
diff --git a/src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableSkullBlockEntityRenderer.java b/src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableSkullBlockEntityRenderer.java
new file mode 100644
index 0000000..9905af1
--- /dev/null
+++ b/src/main/java/moe/nea/firmament/mixins/render/entitytints/UseOverlayableSkullBlockEntityRenderer.java
@@ -0,0 +1,25 @@
+package moe.nea.firmament.mixins.render.entitytints;
+
+import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
+import moe.nea.firmament.events.EntityRenderTintEvent;
+import net.minecraft.client.render.OverlayTexture;
+import net.minecraft.client.render.RenderLayer;
+import net.minecraft.client.render.block.entity.SkullBlockEntityRenderer;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+
+/**
+ * Patch to make {@link SkullBlockEntityRenderer} use a {@link RenderLayer} that allows uses Minecraft's overlay texture, if a {@link EntityRenderTintEvent#overlayOverride} is specified.
+ */
+
+@Mixin(SkullBlockEntityRenderer.class)
+public class UseOverlayableSkullBlockEntityRenderer {
+ @ModifyExpressionValue(method = "renderSkull",
+ at = @At(value = "FIELD", target = "Lnet/minecraft/client/render/OverlayTexture;DEFAULT_UV:I"))
+ private static int replaceUvIndex(int original) {
+ if (EntityRenderTintEvent.overlayOverride != null)
+ return OverlayTexture.packUv(15, 10); // TODO: store this info in a global alongside overlayOverride
+ return original;
+ }
+
+}
diff --git a/src/main/kotlin/commands/rome.kt b/src/main/kotlin/commands/rome.kt
index 13acb3c..5412792 100644
--- a/src/main/kotlin/commands/rome.kt
+++ b/src/main/kotlin/commands/rome.kt
@@ -254,6 +254,7 @@ fun firmamentCommand() = literal("firmament") {
val player = MC.player ?: return@thenExecute
player.world.getOtherEntities(player, player.boundingBox.expand(12.0))
.forEach(PowerUserTools::showEntity)
+ PowerUserTools.showEntity(player)
}
}
thenLiteral("callUrsa") {
diff --git a/src/main/kotlin/events/EntityRenderTintEvent.kt b/src/main/kotlin/events/EntityRenderTintEvent.kt
new file mode 100644
index 0000000..29b888b
--- /dev/null
+++ b/src/main/kotlin/events/EntityRenderTintEvent.kt
@@ -0,0 +1,66 @@
+package moe.nea.firmament.events
+
+import net.minecraft.client.render.GameRenderer
+import net.minecraft.client.render.OverlayTexture
+import net.minecraft.client.render.entity.state.EntityRenderState
+import net.minecraft.entity.Entity
+import net.minecraft.entity.LivingEntity
+import moe.nea.firmament.util.render.TintedOverlayTexture
+
+/**
+ * Change the tint color of a [LivingEntity]
+ */
+class EntityRenderTintEvent(
+ val entity: Entity,
+ val renderState: HasTintRenderState
+) : FirmamentEvent.Cancellable() {
+ init {
+ if (entity !is LivingEntity) {
+ cancel()
+ }
+ }
+
+ companion object : FirmamentEventBus<EntityRenderTintEvent>() {
+ /**
+ * Static variable containing an override for [GameRenderer.getOverlayTexture]. Should be only set briefly.
+ *
+ * This variable only affects render layers that naturally make use of the overlay texture, have proper overlay UVs set (`overlay u != 0`), and have a shader that makes use of the overlay (does not have the `NO_OVERLAY` flag set in its json definition).
+ *
+ * Currently supported layers: [net.minecraft.client.render.entity.equipment.EquipmentRenderer], [net.minecraft.client.render.entity.model.PlayerEntityModel], as well as some others naturally.
+ *
+ * @see moe.nea.firmament.mixins.render.entitytints.ReplaceOverlayTexture
+ * @see TintedOverlayTexture
+ */
+ @JvmField
+ var overlayOverride: OverlayTexture? = null
+ }
+
+ @Suppress("PropertyName", "FunctionName")
+ interface HasTintRenderState {
+ /**
+ * Multiplicative tint applied before the overlay.
+ */
+ var tint_firmament: Int
+
+ /**
+ * Must be set for [tint_firmament] to have any effect.
+ */
+ var hasTintOverride_firmament: Boolean
+
+ // TODO: allow for more specific selection of which layers get tinted
+ /**
+ * Specify a [TintedOverlayTexture] to be used. This does not apply to render layers not using the overlay texture.
+ * @see overlayOverride
+ */
+ var overlayTexture_firmament: TintedOverlayTexture?
+ fun reset_firmament()
+
+ companion object {
+ @JvmStatic
+ fun cast(state: EntityRenderState): HasTintRenderState {
+ return state as HasTintRenderState
+ }
+ }
+ }
+
+}
diff --git a/src/main/kotlin/features/debug/PowerUserTools.kt b/src/main/kotlin/features/debug/PowerUserTools.kt
index ac7a6fb..8be5d5d 100644
--- a/src/main/kotlin/features/debug/PowerUserTools.kt
+++ b/src/main/kotlin/features/debug/PowerUserTools.kt
@@ -1,22 +1,31 @@
package moe.nea.firmament.features.debug
+import com.mojang.serialization.Codec
+import com.mojang.serialization.DynamicOps
import com.mojang.serialization.JsonOps
+import com.mojang.serialization.codecs.RecordCodecBuilder
import kotlin.jvm.optionals.getOrNull
import net.minecraft.block.SkullBlock
import net.minecraft.block.entity.SkullBlockEntity
import net.minecraft.component.DataComponentTypes
import net.minecraft.component.type.ProfileComponent
import net.minecraft.entity.Entity
+import net.minecraft.entity.EntityType
import net.minecraft.entity.LivingEntity
import net.minecraft.item.ItemStack
import net.minecraft.item.Items
+import net.minecraft.nbt.NbtCompound
import net.minecraft.nbt.NbtOps
+import net.minecraft.nbt.NbtString
+import net.minecraft.predicate.NbtPredicate
import net.minecraft.text.Text
import net.minecraft.text.TextCodecs
import net.minecraft.util.Identifier
import net.minecraft.util.hit.BlockHitResult
import net.minecraft.util.hit.EntityHitResult
import net.minecraft.util.hit.HitResult
+import net.minecraft.util.math.Position
+import net.minecraft.util.math.Vec3d
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.CustomItemModelEvent
import moe.nea.firmament.events.HandledScreenKeyPressedEvent
@@ -31,6 +40,7 @@ import moe.nea.firmament.util.ClipboardUtils
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.focusedItemStack
import moe.nea.firmament.util.mc.IntrospectableItemModelManager
+import moe.nea.firmament.util.mc.SNbtFormatter
import moe.nea.firmament.util.mc.SNbtFormatter.Companion.toPrettyString
import moe.nea.firmament.util.mc.displayNameAccordingToNbt
import moe.nea.firmament.util.mc.loreAccordingToNbt
@@ -88,6 +98,11 @@ object PowerUserTools : FirmamentFeature {
}
fun showEntity(target: Entity) {
+ val nbt = NbtPredicate.entityToNbt(target)
+ nbt.remove("Inventory")
+ nbt.put("StyledName", TextCodecs.CODEC.encodeStart(NbtOps.INSTANCE, target.styledDisplayName).orThrow)
+ println(SNbtFormatter.prettify(nbt))
+ ClipboardUtils.setTextContent(SNbtFormatter.prettify(nbt))
MC.sendChat(Text.translatable("firmament.poweruser.entity.type", target.type))
MC.sendChat(Text.translatable("firmament.poweruser.entity.name", target.name))
MC.sendChat(Text.stringifiedTranslatable("firmament.poweruser.entity.position", target.pos))
diff --git a/src/main/kotlin/features/events/anniversity/CenturyRaffleFeatures.kt b/src/main/kotlin/features/events/anniversity/CenturyRaffleFeatures.kt
new file mode 100644
index 0000000..9935051
--- /dev/null
+++ b/src/main/kotlin/features/events/anniversity/CenturyRaffleFeatures.kt
@@ -0,0 +1,63 @@
+package moe.nea.firmament.features.events.anniversity
+
+import java.util.Optional
+import me.shedaniel.math.Color
+import kotlin.jvm.optionals.getOrNull
+import net.minecraft.entity.player.PlayerEntity
+import net.minecraft.text.Style
+import net.minecraft.util.Formatting
+import moe.nea.firmament.annotations.Subscribe
+import moe.nea.firmament.events.EntityRenderTintEvent
+import moe.nea.firmament.gui.config.ManagedConfig
+import moe.nea.firmament.util.MC
+import moe.nea.firmament.util.SkyblockId
+import moe.nea.firmament.util.render.TintedOverlayTexture
+import moe.nea.firmament.util.skyBlockId
+import moe.nea.firmament.util.skyblock.SkyBlockItems
+
+object CenturyRaffleFeatures {
+ object TConfig : ManagedConfig("centuryraffle", Category.EVENTS) {
+ val highlightPlayersForSlice by toggle("highlight-cake-players") { true }
+// val highlightAllPlayers by toggle("highlight-all-cake-players") { true }
+ }
+
+ val cakeIcon = "⛃"
+
+ val cakeColors = listOf(
+ CakeTeam(SkyBlockItems.SLICE_OF_BLUEBERRY_CAKE, Formatting.BLUE),
+ CakeTeam(SkyBlockItems.SLICE_OF_CHEESECAKE, Formatting.YELLOW),
+ CakeTeam(SkyBlockItems.SLICE_OF_GREEN_VELVET_CAKE, Formatting.GREEN),
+ CakeTeam(SkyBlockItems.SLICE_OF_RED_VELVET_CAKE, Formatting.RED),
+ CakeTeam(SkyBlockItems.SLICE_OF_STRAWBERRY_SHORTCAKE, Formatting.LIGHT_PURPLE),
+ )
+
+ data class CakeTeam(
+ val id: SkyblockId,
+ val formatting: Formatting,
+ ) {
+ val searchedTextRgb = formatting.colorValue!!
+ val brightenedRgb = Color.ofOpaque(searchedTextRgb)//.brighter(2.0)
+ val tintOverlay by lazy {
+ TintedOverlayTexture().setColor(brightenedRgb)
+ }
+ }
+
+ val sliceToColor = cakeColors.associateBy { it.id }
+
+ @Subscribe
+ fun onEntityRender(event: EntityRenderTintEvent) {
+ if (!TConfig.highlightPlayersForSlice) return
+ val requestedCakeTeam = sliceToColor[MC.stackInHand?.skyBlockId] ?: return
+ // TODO: cache the requested color
+ val player = event.entity as? PlayerEntity ?: return
+ val cakeColor: Style = player.styledDisplayName.visit(
+ { style, text ->
+ if (text == cakeIcon) Optional.of(style)
+ else Optional.empty()
+ }, Style.EMPTY).getOrNull() ?: return
+ if (cakeColor.color?.rgb == requestedCakeTeam.searchedTextRgb) {
+ event.renderState.overlayTexture_firmament = requestedCakeTeam.tintOverlay
+ }
+ }
+
+}
diff --git a/src/main/kotlin/util/MC.kt b/src/main/kotlin/util/MC.kt
index 215d2a8..ca3742d 100644
--- a/src/main/kotlin/util/MC.kt
+++ b/src/main/kotlin/util/MC.kt
@@ -7,11 +7,13 @@ 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
@@ -85,6 +87,7 @@ object MC {
inline val resourceManager get() = (instance.resourceManager as ReloadableResourceManagerImpl)
inline val itemRenderer: ItemRenderer get() = instance.itemRenderer
inline val worldRenderer: WorldRenderer get() = instance.worldRenderer
+ 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
@@ -96,6 +99,7 @@ object MC {
inline val soundManager get() = instance.soundManager
inline val player: ClientPlayerEntity? get() = TestUtil.unlessTesting { instance.player }
inline val camera: Entity? get() = instance.cameraEntity
+ inline val stackInHand: ItemStack? get() = player?.inventory?.mainHandStack
inline val guiAtlasManager get() = instance.guiAtlasManager
inline val world: ClientWorld? get() = TestUtil.unlessTesting { instance.world }
inline val playerName: String? get() = player?.name?.unformattedString
diff --git a/src/main/kotlin/util/render/TintedOverlayTexture.kt b/src/main/kotlin/util/render/TintedOverlayTexture.kt
new file mode 100644
index 0000000..a02eccc
--- /dev/null
+++ b/src/main/kotlin/util/render/TintedOverlayTexture.kt
@@ -0,0 +1,44 @@
+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 moe.nea.firmament.util.ErrorUtil
+
+class TintedOverlayTexture : OverlayTexture() {
+ companion object {
+ val size = 16
+ }
+
+ private var lastColor: Color? = null
+ fun setColor(color: Color): TintedOverlayTexture {
+ val image = ErrorUtil.notNullOr(texture.image, "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())
+ } else {
+ val k = ((1F - j / 15F * 0.75F) * 255F).toInt()
+ image.setColorArgb(j, i, ColorHelper.withAlpha(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)
+ return this
+ }
+}
diff --git a/src/main/kotlin/util/skyblock/SkyBlockItems.kt b/src/main/kotlin/util/skyblock/SkyBlockItems.kt
index cfd8429..ca2b17b 100644
--- a/src/main/kotlin/util/skyblock/SkyBlockItems.kt
+++ b/src/main/kotlin/util/skyblock/SkyBlockItems.kt
@@ -8,4 +8,9 @@ object SkyBlockItems {
val DIAMOND = SkyblockId("DIAMOND")
val ANCESTRAL_SPADE = SkyblockId("ANCESTRAL_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")
}
diff --git a/src/main/resources/firmament.accesswidener b/src/main/resources/firmament.accesswidener
index 7418529..1805721 100644
--- a/src/main/resources/firmament.accesswidener
+++ b/src/main/resources/firmament.accesswidener
@@ -20,3 +20,8 @@ mutable field net/minecraft/screen/slot/Slot y I
accessible field net/minecraft/entity/player/PlayerEntity PLAYER_MODEL_PARTS Lnet/minecraft/entity/data/TrackedData;
accessible field net/minecraft/client/render/WorldRenderer chunks Lnet/minecraft/client/render/BuiltChunkStorage;
+accessible field net/minecraft/client/render/OverlayTexture texture Lnet/minecraft/client/texture/NativeImageBackedTexture;
+
+accessible method net/minecraft/client/render/RenderPhase$Texture getId ()Ljava/util/Optional;
+accessible field net/minecraft/client/render/RenderLayer$MultiPhase phases Lnet/minecraft/client/render/RenderLayer$MultiPhaseParameters;
+accessible field net/minecraft/client/render/RenderLayer$MultiPhaseParameters texture Lnet/minecraft/client/render/RenderPhase$TextureBase;
diff --git a/translations/en_us.json b/translations/en_us.json
index 1432332..1d0a8ed 100644
--- a/translations/en_us.json
+++ b/translations/en_us.json
@@ -44,6 +44,9 @@
"firmament.config.category.mining.description": "Mining related features",
"firmament.config.category.misc": "Miscellaneous",
"firmament.config.category.misc.description": "Miscellaneous features that don't fit elsewhere",
+ "firmament.config.centuryraffle": "Century Raffle",
+ "firmament.config.centuryraffle.highlight-cake-players": "Highlight Players in Team",
+ "firmament.config.centuryraffle.highlight-cake-players.description": "Highlight the players you can feed while holding a Slice of Century Cake",
"firmament.config.chat-links": "Chat Links",
"firmament.config.chat-links.allow-all-hosts": "Allow all Image Hosts",
"firmament.config.chat-links.allow-all-hosts.description": "Allow displaying images no matter where it is hosted.",