feat: Add interactive component movers + fix: move coordinates

This commit is contained in:
Linnea Gräf
2025-06-22 14:51:38 +02:00
parent db0174ca8c
commit 4cfba87783
7 changed files with 261 additions and 109 deletions

View File

@@ -2,7 +2,8 @@ package moe.nea.firmament.features.texturepack
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import net.minecraft.client.MinecraftClient
import kotlinx.serialization.Transient
import net.minecraft.client.font.TextRenderer
import net.minecraft.client.gui.DrawContext
import net.minecraft.client.gui.screen.Screen
import net.minecraft.client.gui.screen.ingame.HandledScreen
@@ -17,7 +18,9 @@ import moe.nea.firmament.Firmament
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.FinalizeResourceManagerEvent
import moe.nea.firmament.events.ScreenChangeEvent
import moe.nea.firmament.features.texturepack.CustomTextColors.cache
import moe.nea.firmament.features.texturepack.CustomScreenLayouts.Alignment.CENTER
import moe.nea.firmament.features.texturepack.CustomScreenLayouts.Alignment.LEFT
import moe.nea.firmament.features.texturepack.CustomScreenLayouts.Alignment.RIGHT
import moe.nea.firmament.mixins.accessor.AccessorHandledScreen
import moe.nea.firmament.util.ErrorUtil.intoCatch
import moe.nea.firmament.util.IdentifierSerializer
@@ -29,8 +32,18 @@ object CustomScreenLayouts : SinglePreparationResourceReloader<List<CustomScreen
val predicates: Preds,
val background: BackgroundReplacer? = null,
val slots: List<SlotReplacer> = listOf(),
val playerTitle: TitleReplacer = TitleReplacer(),
val containerTitle: TitleReplacer = TitleReplacer()
val playerTitle: TitleReplacer? = null,
val containerTitle: TitleReplacer? = null,
val repairCostTitle: TitleReplacer? = null,
val nameField: ComponentMover? = null,
)
@Serializable
data class ComponentMover(
val x: Int,
val y: Int,
val width: Int? = null,
val height: Int? = null,
)
@Serializable
@@ -97,49 +110,48 @@ object CustomScreenLayouts : SinglePreparationResourceReloader<List<CustomScreen
enum class Alignment {
@SerialName("left")
LEFT,
@SerialName("center")
CENTER,
@SerialName("right")
RIGHT
}
@Serializable
data class TitleReplacer(
val x: Int = 0,
val y: Int = 0,
val x: Int? = null,
val y: Int? = null,
val align: Alignment = Alignment.LEFT,
val replace: String? = null
)
) {
@Transient
val replacedText: Text? = replace?.let(Text::literal)
fun alignText(text: Text, x: Int, width: Int): Int {
var currentText = mapReplaceText(text)
val align = if (currentText.string == "Inventory") activeScreenOverride?.playerTitle?.align ?: Alignment.LEFT
else activeScreenOverride?.containerTitle?.align ?: Alignment.LEFT
val textWidth = MinecraftClient.getInstance().textRenderer.getWidth(Text.literal(currentText.string))
return when (align) {
Alignment.LEFT -> x
Alignment.CENTER -> x + (width - textWidth) / 2
Alignment.RIGHT -> x + (width - textWidth)
fun replaceText(text: Text): Text {
if (replacedText != null) return replacedText
return text
}
}
fun mapReplaceText(text: Text): Text {
val replaceText = if (text.string == "Inventory") activeScreenOverride?.playerTitle?.replace ?: null else activeScreenOverride?.containerTitle?.replace ?: null
if (replaceText == null) return text
return Text.literal(replaceText)
}
fun replaceY(y: Int): Int {
return this.y ?: y
}
fun mapTextToX(text: Text, x: Int): Int {
return x + if (text.string == "Inventory") activeScreenOverride?.playerTitle?.x
?: 0 else activeScreenOverride?.containerTitle?.x ?: 0
}
fun replaceX(font: TextRenderer, text: Text, x: Int): Int {
val baseX = this.x ?: x
return baseX + when (this.align) {
LEFT -> 0
CENTER -> -font.getWidth(text) / 2
RIGHT -> -font.getWidth(text)
}
}
fun mapTextToY(text: Text, y: Int): Int {
return y + if (text.string == "Inventory") activeScreenOverride?.playerTitle?.y
?: 0 else activeScreenOverride?.containerTitle?.y ?: 0
/**
* Not technically part of the package, but it does allow for us to later on seamlessly integrate a color option into this class as well
*/
fun replaceColor(text: Text, color: Int): Int {
return CustomTextColors.mapTextColor(text, color)
}
}
@@ -175,6 +187,16 @@ object CustomScreenLayouts : SinglePreparationResourceReloader<List<CustomScreen
@get:JvmStatic
var activeScreenOverride = null as CustomScreenLayout?
val DO_NOTHING_TEXT_REPLACER = TitleReplacer()
@JvmStatic
fun <T>getMover(selector: (CustomScreenLayout)-> (T?)) =
activeScreenOverride?.let(selector)
@JvmStatic
fun getTextMover(selector: (CustomScreenLayout) -> (TitleReplacer?)) =
getMover(selector) ?: DO_NOTHING_TEXT_REPLACER
@Subscribe
fun onScreenOpen(event: ScreenChangeEvent) {
if (!CustomSkyBlockTextures.TConfig.allowLayoutChanges) {

View File

@@ -1,51 +0,0 @@
package moe.nea.firmament.mixins.custommodels;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import moe.nea.firmament.features.texturepack.CustomScreenLayouts;
import moe.nea.firmament.features.texturepack.CustomTextColors;
import moe.nea.firmament.mixins.accessor.AccessorHandledScreen;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.screen.ingame.AnvilScreen;
import net.minecraft.client.gui.screen.ingame.BeaconScreen;
import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen;
import net.minecraft.client.gui.screen.ingame.HandledScreen;
import net.minecraft.client.gui.screen.ingame.InventoryScreen;
import net.minecraft.client.gui.screen.ingame.MerchantScreen;
import net.minecraft.text.Text;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@Mixin({HandledScreen.class, InventoryScreen.class, CreativeInventoryScreen.class, MerchantScreen.class,
AnvilScreen.class, BeaconScreen.class})
public class ReplaceTextColorInHandledScreen {
// To my future self: double check those mixins, but don't be too concerned about errors. Some of the wrapopertions
// only apply in some of the specified subclasses.
@WrapOperation(
method = "drawForeground",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/client/gui/DrawContext;drawText(Lnet/minecraft/client/font/TextRenderer;Lnet/minecraft/text/Text;IIIZ)I"),
expect = 0,
require = 0)
private int replaceTextColorWithVariableShadow(DrawContext instance, TextRenderer textRenderer, Text text, int x, int y, int color, boolean shadow, Operation<Integer> original) {
int width = ((AccessorHandledScreen) this).getBackgroundWidth_Firmament();
return original.call(instance, textRenderer, CustomScreenLayouts.INSTANCE.mapReplaceText(text), CustomScreenLayouts.INSTANCE.alignText(text, CustomScreenLayouts.INSTANCE.mapTextToX(text, x), width), CustomScreenLayouts.INSTANCE.mapTextToY(text, y), CustomTextColors.INSTANCE.mapTextColor(text, color), shadow);
}
@WrapOperation(
method = "drawForeground",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/client/gui/DrawContext;drawTextWithShadow(Lnet/minecraft/client/font/TextRenderer;Lnet/minecraft/text/Text;III)I"),
expect = 0,
require = 0)
private int replaceTextColorWithShadow(DrawContext instance, TextRenderer textRenderer, Text text, int x, int y, int color, Operation<Integer> original) {
int width = ((AccessorHandledScreen) this).getBackgroundWidth_Firmament();
return original.call(instance, textRenderer, CustomScreenLayouts.INSTANCE.mapReplaceText(text), CustomScreenLayouts.INSTANCE.alignText(text, CustomScreenLayouts.INSTANCE.mapTextToX(text, x), width), CustomScreenLayouts.INSTANCE.mapTextToY(text, y), CustomTextColors.INSTANCE.mapTextColor(text, color));
}
}

View File

@@ -0,0 +1,55 @@
package moe.nea.firmament.mixins.custommodels.screenlayouts;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import moe.nea.firmament.features.texturepack.CustomScreenLayouts;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.screen.ingame.AnvilScreen;
import net.minecraft.client.gui.screen.ingame.ForgingScreen;
import net.minecraft.client.gui.widget.TextFieldWidget;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.screen.AnvilScreenHandler;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(AnvilScreen.class)
public abstract class ReplaceAnvilScreen extends ForgingScreen<AnvilScreenHandler> {
@Shadow
private TextFieldWidget nameField;
public ReplaceAnvilScreen(AnvilScreenHandler handler, PlayerInventory playerInventory, Text title, Identifier texture) {
super(handler, playerInventory, title, texture);
}
@Inject(method = "setup", at = @At("TAIL"))
private void moveNameField(CallbackInfo ci) {
var override = CustomScreenLayouts.getMover(CustomScreenLayouts.CustomScreenLayout::getNameField);
if (override == null) return;
int baseX = (this.width - this.backgroundWidth) / 2;
int baseY = (this.height - this.backgroundHeight) / 2;
nameField.setX(baseX + override.getX());
nameField.setY(baseY + override.getY());
if (override.getWidth() != null)
nameField.setWidth(override.getWidth());
if (override.getHeight() != null)
nameField.setHeight(override.getHeight());
}
@WrapOperation(method = "drawForeground",
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/DrawContext;drawTextWithShadow(Lnet/minecraft/client/font/TextRenderer;Lnet/minecraft/text/Text;III)I"),
allow = 1)
private int onDrawRepairCost(DrawContext instance, TextRenderer textRenderer, Text text, int x, int y, int color, Operation<Integer> original) {
var textOverride = CustomScreenLayouts.getTextMover(CustomScreenLayouts.CustomScreenLayout::getRepairCostTitle);
return original.call(instance, textRenderer,
textOverride.replaceText(text),
textOverride.replaceX(textRenderer, text, x),
textOverride.replaceY(y),
textOverride.replaceColor(text, color));
}
}

View File

@@ -0,0 +1,9 @@
package moe.nea.firmament.mixins.custommodels.screenlayouts;
import net.minecraft.client.gui.screen.ingame.ForgingScreen;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.Inject;
@Mixin(ForgingScreen.class)
public class ReplaceForgingScreen {
}

View File

@@ -1,7 +1,10 @@
package moe.nea.firmament.mixins.custommodels.screenlayouts;
import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import moe.nea.firmament.features.texturepack.CustomScreenLayouts;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.screen.ingame.InventoryScreen;
import net.minecraft.client.gui.screen.ingame.RecipeBookScreen;
@@ -22,6 +25,20 @@ public abstract class ReplacePlayerBackgrounds extends RecipeBookScreen<PlayerSc
super(handler, recipeBook, inventory, title);
}
@WrapOperation(method = "drawForeground",
allow = 1,
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/DrawContext;drawText(Lnet/minecraft/client/font/TextRenderer;Lnet/minecraft/text/Text;IIIZ)I"))
private int onDrawForegroundText(DrawContext instance, TextRenderer textRenderer, Text text, int x, int y, int color, boolean shadow, Operation<Integer> original) {
var textOverride = CustomScreenLayouts.getTextMover(CustomScreenLayouts.CustomScreenLayout::getContainerTitle);
return original.call(instance, textRenderer,
textOverride.replaceText(text),
textOverride.replaceX(textRenderer, text, x),
textOverride.replaceY(y),
textOverride.replaceColor(text, color),
shadow);
}
@WrapWithCondition(method = "drawBackground", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/DrawContext;drawTexture(Ljava/util/function/Function;Lnet/minecraft/util/Identifier;IIFFIIII)V"))
private boolean onDrawBackground(DrawContext instance, Function<Identifier, RenderLayer> renderLayers, Identifier sprite, int x, int y, float u, float v, int width, int height, int textureWidth, int textureHeight) {
final var override = CustomScreenLayouts.getActiveScreenOverride();

View File

@@ -0,0 +1,65 @@
package moe.nea.firmament.mixins.custommodels.screenlayouts;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import moe.nea.firmament.features.texturepack.CustomScreenLayouts;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.screen.ingame.AnvilScreen;
import net.minecraft.client.gui.screen.ingame.BeaconScreen;
import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen;
import net.minecraft.client.gui.screen.ingame.HandledScreen;
import net.minecraft.client.gui.screen.ingame.InventoryScreen;
import net.minecraft.client.gui.screen.ingame.MerchantScreen;
import net.minecraft.text.Text;
import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Slice;
@Mixin(HandledScreen.class)
// TODO: MerchantScreen.class, BeaconScreen.class
public class ReplaceTextColorInHandledScreen {
@WrapOperation(
method = "drawForeground",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/client/gui/DrawContext;drawText(Lnet/minecraft/client/font/TextRenderer;Lnet/minecraft/text/Text;IIIZ)I"),
slice = @Slice(
from = @At(value = "FIELD", target = "Lnet/minecraft/client/gui/screen/ingame/HandledScreen;title:Lnet/minecraft/text/Text;", opcode = Opcodes.GETFIELD),
to = @At(value = "FIELD", target = "Lnet/minecraft/client/gui/screen/ingame/HandledScreen;playerInventoryTitle:Lnet/minecraft/text/Text;", opcode = Opcodes.GETFIELD)
),
allow = 1,
require = 1)
private int replaceContainerTitle(DrawContext instance, TextRenderer textRenderer, Text text, int x, int y, int color, boolean shadow, Operation<Integer> original) {
var textOverride = CustomScreenLayouts.getTextMover(CustomScreenLayouts.CustomScreenLayout::getContainerTitle);
return original.call(instance, textRenderer,
textOverride.replaceText(text),
textOverride.replaceX(textRenderer, text, x),
textOverride.replaceY(y),
textOverride.replaceColor(text, color),
shadow);
}
@WrapOperation(
method = "drawForeground",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/client/gui/DrawContext;drawText(Lnet/minecraft/client/font/TextRenderer;Lnet/minecraft/text/Text;IIIZ)I"),
slice = @Slice(
from = @At(value = "FIELD", target = "Lnet/minecraft/client/gui/screen/ingame/HandledScreen;playerInventoryTitle:Lnet/minecraft/text/Text;", opcode = Opcodes.GETFIELD),
to = @At(value = "TAIL")
),
allow = 1,
require = 1)
private int replacePlayerShadow(DrawContext instance, TextRenderer textRenderer, Text text, int x, int y, int color, boolean shadow, Operation<Integer> original) {
var textOverride = CustomScreenLayouts.getTextMover(CustomScreenLayouts.CustomScreenLayout::getContainerTitle);
return original.call(instance, textRenderer,
textOverride.replaceText(text),
textOverride.replaceX(textRenderer, text, x),
textOverride.replaceY(y),
textOverride.replaceColor(text, color),
shadow);
}
}