feat: Highlight century cake slice players

This commit is contained in:
Linnea Gräf
2025-02-02 00:08:18 +01:00
parent c2aefe09ab
commit 89bceb6735
18 changed files with 559 additions and 3 deletions

View File

@@ -6,15 +6,21 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import com.google.common.hash.Hashing
import com.google.devtools.ksp.gradle.KspTaskJvm import com.google.devtools.ksp.gradle.KspTaskJvm
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.JsonObject import com.google.gson.JsonObject
import moe.nea.licenseextractificator.LicenseDiscoveryTask import moe.nea.licenseextractificator.LicenseDiscoveryTask
import moe.nea.mcautotranslations.gradle.CollectTranslations import moe.nea.mcautotranslations.gradle.CollectTranslations
import net.fabricmc.loom.LoomGradleExtension 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.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.plugin.SubpluginOption 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 org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import java.nio.charset.StandardCharsets
import java.util.Base64
plugins { plugins {
java java
@@ -337,9 +343,9 @@ loom {
named("client") { named("client") {
property("devauth.enabled", "true") property("devauth.enabled", "true")
vmArg("-ea") vmArg("-ea")
vmArg("-XX:+AllowEnhancedClassRedefinition") // vmArg("-XX:+AllowEnhancedClassRedefinition")
vmArg("-XX:HotswapAgent=external") // vmArg("-XX:HotswapAgent=external")
vmArg("-javaagent:${hotswap.resolve().single().absolutePath}") // vmArg("-javaagent:${hotswap.resolve().single().absolutePath}")
} }
} }
} }
@@ -463,6 +469,70 @@ tasks.create("printAllLicenses", LicenseDiscoveryTask::class.java, licensing).ap
} }
outputs.upToDateWhen { false } 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 { tasks.withType<AbstractArchiveTask>().configureEach {
isPreserveFileTimestamps = false isPreserveFileTimestamps = false

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -254,6 +254,7 @@ fun firmamentCommand() = literal("firmament") {
val player = MC.player ?: return@thenExecute val player = MC.player ?: return@thenExecute
player.world.getOtherEntities(player, player.boundingBox.expand(12.0)) player.world.getOtherEntities(player, player.boundingBox.expand(12.0))
.forEach(PowerUserTools::showEntity) .forEach(PowerUserTools::showEntity)
PowerUserTools.showEntity(player)
} }
} }
thenLiteral("callUrsa") { thenLiteral("callUrsa") {

View File

@@ -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
}
}
}
}

View File

@@ -1,22 +1,31 @@
package moe.nea.firmament.features.debug 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.JsonOps
import com.mojang.serialization.codecs.RecordCodecBuilder
import kotlin.jvm.optionals.getOrNull import kotlin.jvm.optionals.getOrNull
import net.minecraft.block.SkullBlock import net.minecraft.block.SkullBlock
import net.minecraft.block.entity.SkullBlockEntity import net.minecraft.block.entity.SkullBlockEntity
import net.minecraft.component.DataComponentTypes import net.minecraft.component.DataComponentTypes
import net.minecraft.component.type.ProfileComponent import net.minecraft.component.type.ProfileComponent
import net.minecraft.entity.Entity import net.minecraft.entity.Entity
import net.minecraft.entity.EntityType
import net.minecraft.entity.LivingEntity import net.minecraft.entity.LivingEntity
import net.minecraft.item.ItemStack import net.minecraft.item.ItemStack
import net.minecraft.item.Items import net.minecraft.item.Items
import net.minecraft.nbt.NbtCompound
import net.minecraft.nbt.NbtOps import net.minecraft.nbt.NbtOps
import net.minecraft.nbt.NbtString
import net.minecraft.predicate.NbtPredicate
import net.minecraft.text.Text import net.minecraft.text.Text
import net.minecraft.text.TextCodecs import net.minecraft.text.TextCodecs
import net.minecraft.util.Identifier import net.minecraft.util.Identifier
import net.minecraft.util.hit.BlockHitResult import net.minecraft.util.hit.BlockHitResult
import net.minecraft.util.hit.EntityHitResult import net.minecraft.util.hit.EntityHitResult
import net.minecraft.util.hit.HitResult 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.annotations.Subscribe
import moe.nea.firmament.events.CustomItemModelEvent import moe.nea.firmament.events.CustomItemModelEvent
import moe.nea.firmament.events.HandledScreenKeyPressedEvent 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.MC
import moe.nea.firmament.util.focusedItemStack import moe.nea.firmament.util.focusedItemStack
import moe.nea.firmament.util.mc.IntrospectableItemModelManager 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.SNbtFormatter.Companion.toPrettyString
import moe.nea.firmament.util.mc.displayNameAccordingToNbt import moe.nea.firmament.util.mc.displayNameAccordingToNbt
import moe.nea.firmament.util.mc.loreAccordingToNbt import moe.nea.firmament.util.mc.loreAccordingToNbt
@@ -88,6 +98,11 @@ object PowerUserTools : FirmamentFeature {
} }
fun showEntity(target: Entity) { 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.type", target.type))
MC.sendChat(Text.translatable("firmament.poweruser.entity.name", target.name)) MC.sendChat(Text.translatable("firmament.poweruser.entity.name", target.name))
MC.sendChat(Text.stringifiedTranslatable("firmament.poweruser.entity.position", target.pos)) MC.sendChat(Text.stringifiedTranslatable("firmament.poweruser.entity.position", target.pos))

View File

@@ -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
}
}
}

View File

@@ -7,11 +7,13 @@ import net.minecraft.client.gui.hud.InGameHud
import net.minecraft.client.gui.screen.Screen import net.minecraft.client.gui.screen.Screen
import net.minecraft.client.gui.screen.ingame.HandledScreen import net.minecraft.client.gui.screen.ingame.HandledScreen
import net.minecraft.client.network.ClientPlayerEntity import net.minecraft.client.network.ClientPlayerEntity
import net.minecraft.client.render.GameRenderer
import net.minecraft.client.render.WorldRenderer import net.minecraft.client.render.WorldRenderer
import net.minecraft.client.render.item.ItemRenderer import net.minecraft.client.render.item.ItemRenderer
import net.minecraft.client.world.ClientWorld import net.minecraft.client.world.ClientWorld
import net.minecraft.entity.Entity import net.minecraft.entity.Entity
import net.minecraft.item.Item import net.minecraft.item.Item
import net.minecraft.item.ItemStack
import net.minecraft.network.packet.c2s.play.CommandExecutionC2SPacket import net.minecraft.network.packet.c2s.play.CommandExecutionC2SPacket
import net.minecraft.registry.BuiltinRegistries import net.minecraft.registry.BuiltinRegistries
import net.minecraft.registry.RegistryKeys import net.minecraft.registry.RegistryKeys
@@ -85,6 +87,7 @@ object MC {
inline val resourceManager get() = (instance.resourceManager as ReloadableResourceManagerImpl) inline val resourceManager get() = (instance.resourceManager as ReloadableResourceManagerImpl)
inline val itemRenderer: ItemRenderer get() = instance.itemRenderer inline val itemRenderer: ItemRenderer get() = instance.itemRenderer
inline val worldRenderer: WorldRenderer get() = instance.worldRenderer inline val worldRenderer: WorldRenderer get() = instance.worldRenderer
inline val gameRenderer: GameRenderer get() = instance.gameRenderer
inline val networkHandler get() = player?.networkHandler inline val networkHandler get() = player?.networkHandler
inline val instance get() = MinecraftClient.getInstance() inline val instance get() = MinecraftClient.getInstance()
inline val keyboard get() = instance.keyboard inline val keyboard get() = instance.keyboard
@@ -96,6 +99,7 @@ object MC {
inline val soundManager get() = instance.soundManager inline val soundManager get() = instance.soundManager
inline val player: ClientPlayerEntity? get() = TestUtil.unlessTesting { instance.player } inline val player: ClientPlayerEntity? get() = TestUtil.unlessTesting { instance.player }
inline val camera: Entity? get() = instance.cameraEntity inline val camera: Entity? get() = instance.cameraEntity
inline val stackInHand: ItemStack? get() = player?.inventory?.mainHandStack
inline val guiAtlasManager get() = instance.guiAtlasManager inline val guiAtlasManager get() = instance.guiAtlasManager
inline val world: ClientWorld? get() = TestUtil.unlessTesting { instance.world } inline val world: ClientWorld? get() = TestUtil.unlessTesting { instance.world }
inline val playerName: String? get() = player?.name?.unformattedString inline val playerName: String? get() = player?.name?.unformattedString

View File

@@ -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
}
}

View File

@@ -8,4 +8,9 @@ object SkyBlockItems {
val DIAMOND = SkyblockId("DIAMOND") val DIAMOND = SkyblockId("DIAMOND")
val ANCESTRAL_SPADE = SkyblockId("ANCESTRAL_SPADE") val ANCESTRAL_SPADE = SkyblockId("ANCESTRAL_SPADE")
val REFORGE_ANVIL = SkyblockId("REFORGE_ANVIL") 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")
} }

View File

@@ -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/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/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;

View File

@@ -44,6 +44,9 @@
"firmament.config.category.mining.description": "Mining related features", "firmament.config.category.mining.description": "Mining related features",
"firmament.config.category.misc": "Miscellaneous", "firmament.config.category.misc": "Miscellaneous",
"firmament.config.category.misc.description": "Miscellaneous features that don't fit elsewhere", "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": "Chat Links",
"firmament.config.chat-links.allow-all-hosts": "Allow all Image Hosts", "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.", "firmament.config.chat-links.allow-all-hosts.description": "Allow displaying images no matter where it is hosted.",