feat: Highlight century cake slice players
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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") {
|
||||||
|
|||||||
66
src/main/kotlin/events/EntityRenderTintEvent.kt
Normal file
66
src/main/kotlin/events/EntityRenderTintEvent.kt
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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))
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
44
src/main/kotlin/util/render/TintedOverlayTexture.kt
Normal file
44
src/main/kotlin/util/render/TintedOverlayTexture.kt
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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.",
|
||||||
|
|||||||
Reference in New Issue
Block a user