feat: Add screen layout replacement feature for texture packs
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
package moe.nea.firmament.features.texturepack
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import net.minecraft.client.gui.DrawContext
|
||||
import net.minecraft.client.gui.screen.Screen
|
||||
import net.minecraft.client.gui.screen.ingame.HandledScreen
|
||||
import net.minecraft.client.render.RenderLayer
|
||||
import net.minecraft.resource.ResourceManager
|
||||
import net.minecraft.resource.SinglePreparationResourceReloader
|
||||
import net.minecraft.screen.slot.Slot
|
||||
import net.minecraft.util.Identifier
|
||||
import net.minecraft.util.profiler.Profiler
|
||||
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.mixins.accessor.AccessorHandledScreen
|
||||
import moe.nea.firmament.util.ErrorUtil.intoCatch
|
||||
import moe.nea.firmament.util.IdentifierSerializer
|
||||
|
||||
object CustomScreenLayouts : SinglePreparationResourceReloader<List<CustomScreenLayouts.CustomScreenLayout>>() {
|
||||
|
||||
@Serializable
|
||||
data class CustomScreenLayout(
|
||||
val predicates: Preds,
|
||||
val background: BackgroundReplacer? = null,
|
||||
val slots: List<SlotReplacer> = listOf(),
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Preds(
|
||||
val label: StringMatcher,
|
||||
) {
|
||||
fun matches(screen: Screen): Boolean {
|
||||
// TODO: does this deserve the restriction to handled screen
|
||||
val s = screen as? HandledScreen<*>? ?: return false
|
||||
return label.matches(s.title)
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class BackgroundReplacer(
|
||||
@Serializable(with = IdentifierSerializer::class)
|
||||
val texture: Identifier,
|
||||
// TODO: allow selectively still rendering some components (recipe button, trade backgrounds, furnace flame progress, arrows)
|
||||
val x: Int,
|
||||
val y: Int,
|
||||
val width: Int,
|
||||
val height: Int,
|
||||
) {
|
||||
fun renderGeneric(context: DrawContext, screen: HandledScreen<*>) {
|
||||
screen as AccessorHandledScreen
|
||||
val originalX: Int = (screen.width - screen.backgroundWidth_Firmament) / 2
|
||||
val originalY: Int = (screen.height - screen.backgroundHeight_Firmament) / 2
|
||||
val modifiedX = originalX + this.x
|
||||
val modifiedY = originalY + this.y
|
||||
val textureWidth = this.width
|
||||
val textureHeight = this.height
|
||||
context.drawTexture(
|
||||
RenderLayer::getGuiTextured,
|
||||
this.texture,
|
||||
modifiedX,
|
||||
modifiedY,
|
||||
0.0f,
|
||||
0.0f,
|
||||
textureWidth,
|
||||
textureHeight,
|
||||
textureWidth,
|
||||
textureHeight
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class SlotReplacer(
|
||||
// TODO: override getRecipeBookButtonPos as well
|
||||
// TODO: is this index or id (i always forget which one is duplicated per inventory)
|
||||
val index: Int,
|
||||
val x: Int,
|
||||
val y: Int,
|
||||
) {
|
||||
fun move(slots: List<Slot>) {
|
||||
val slot = slots.getOrNull(index) ?: return
|
||||
slot.x = x
|
||||
slot.y = y
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Subscribe
|
||||
fun onStart(event: FinalizeResourceManagerEvent) {
|
||||
event.resourceManager.registerReloader(CustomScreenLayouts)
|
||||
}
|
||||
|
||||
override fun prepare(
|
||||
manager: ResourceManager,
|
||||
profiler: Profiler
|
||||
): List<CustomScreenLayout> {
|
||||
val allScreenLayouts = manager.findResources(
|
||||
"overrides/screen_layout",
|
||||
{ it.path.endsWith(".json") && it.namespace == "firmskyblock" })
|
||||
val allParsedLayouts = allScreenLayouts.mapNotNull { (path, stream) ->
|
||||
Firmament.tryDecodeJsonFromStream<CustomScreenLayout>(stream.inputStream)
|
||||
.intoCatch("Could not read custom screen layout from $path").orNull()
|
||||
}
|
||||
return allParsedLayouts
|
||||
}
|
||||
|
||||
var customScreenLayouts = listOf<CustomScreenLayout>()
|
||||
|
||||
override fun apply(
|
||||
prepared: List<CustomScreenLayout>,
|
||||
manager: ResourceManager?,
|
||||
profiler: Profiler?
|
||||
) {
|
||||
this.customScreenLayouts = prepared
|
||||
}
|
||||
|
||||
@get:JvmStatic
|
||||
var activeScreenOverride = null as CustomScreenLayout?
|
||||
|
||||
@Subscribe
|
||||
fun onScreenOpen(event: ScreenChangeEvent) {
|
||||
if (!CustomSkyBlockTextures.TConfig.allowLayoutChanges) {
|
||||
activeScreenOverride = null
|
||||
return
|
||||
}
|
||||
activeScreenOverride = event.new?.let { screen ->
|
||||
customScreenLayouts.find { it.predicates.matches(screen) }
|
||||
}
|
||||
|
||||
val screen = event.new as? HandledScreen<*> ?: return
|
||||
val handler = screen.screenHandler
|
||||
activeScreenOverride?.let { override ->
|
||||
override.slots.forEach { slotReplacer ->
|
||||
slotReplacer.move(handler.slots)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -36,6 +36,7 @@ object CustomSkyBlockTextures : FirmamentFeature {
|
||||
val enableLegacyMinecraftCompat by toggle("legacy-minecraft-path-support") { true }
|
||||
val enableLegacyCIT by toggle("legacy-cit") { true }
|
||||
val allowRecoloringUiText by toggle("recolor-text") { true }
|
||||
val allowLayoutChanges by toggle("screen-layouts") { true }
|
||||
}
|
||||
|
||||
override val config: ManagedConfig
|
||||
|
||||
@@ -2,6 +2,7 @@ package moe.nea.firmament.features.texturepack
|
||||
|
||||
import java.util.Optional
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
import net.minecraft.resource.ResourceManager
|
||||
import net.minecraft.resource.SinglePreparationResourceReloader
|
||||
@@ -18,12 +19,23 @@ object CustomTextColors : SinglePreparationResourceReloader<CustomTextColors.Tex
|
||||
data class TextOverrides(
|
||||
val defaultColor: Int,
|
||||
val overrides: List<TextOverride> = listOf()
|
||||
)
|
||||
) {
|
||||
/**
|
||||
* Stub custom text color to allow always returning a text override
|
||||
*/
|
||||
@Transient
|
||||
val baseOverride = TextOverride(
|
||||
StringMatcher.Equals("", false),
|
||||
defaultColor,
|
||||
false
|
||||
)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class TextOverride(
|
||||
val predicate: StringMatcher,
|
||||
val override: Int,
|
||||
val hidden: Boolean = false,
|
||||
)
|
||||
|
||||
@Subscribe
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package moe.nea.firmament.mixins.custommodels.screenlayouts;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
|
||||
import moe.nea.firmament.features.texturepack.CustomScreenLayouts;
|
||||
import net.minecraft.client.gui.DrawContext;
|
||||
import net.minecraft.client.gui.screen.ingame.AbstractFurnaceScreen;
|
||||
import net.minecraft.client.gui.screen.ingame.RecipeBookScreen;
|
||||
import net.minecraft.client.gui.screen.recipebook.RecipeBookWidget;
|
||||
import net.minecraft.client.render.RenderLayer;
|
||||
import net.minecraft.entity.player.PlayerInventory;
|
||||
import net.minecraft.screen.AbstractFurnaceScreenHandler;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.util.Identifier;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
@Mixin(AbstractFurnaceScreen.class)
|
||||
public abstract class ReplaceFurnaceBackgrounds<T extends AbstractFurnaceScreenHandler> extends RecipeBookScreen<T> {
|
||||
public ReplaceFurnaceBackgrounds(T handler, RecipeBookWidget<?> recipeBook, PlayerInventory inventory, Text title) {
|
||||
super(handler, recipeBook, inventory, title);
|
||||
}
|
||||
|
||||
@WrapWithCondition(method = "drawBackground", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/DrawContext;drawTexture(Ljava/util/function/Function;Lnet/minecraft/util/Identifier;IIFFIIII)V"), allow = 1)
|
||||
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();
|
||||
if (override == null || override.getBackground() == null) return true;
|
||||
override.getBackground().renderGeneric(instance, this);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package moe.nea.firmament.mixins.custommodels.screenlayouts;
|
||||
|
||||
|
||||
import moe.nea.firmament.features.texturepack.CustomScreenLayouts;
|
||||
import net.minecraft.client.gui.DrawContext;
|
||||
import net.minecraft.client.gui.screen.ingame.*;
|
||||
import net.minecraft.entity.player.PlayerInventory;
|
||||
import net.minecraft.screen.ScreenHandler;
|
||||
import net.minecraft.text.Text;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin({CraftingScreen.class, CrafterScreen.class, Generic3x3ContainerScreen.class, GenericContainerScreen.class, HopperScreen.class, ShulkerBoxScreen.class,})
|
||||
public abstract class ReplaceGenericBackgrounds extends HandledScreen<ScreenHandler> {
|
||||
// TODO: split out screens with special background components like flames, arrows, etc. (maybe arrows deserve generic handling tho)
|
||||
public ReplaceGenericBackgrounds(ScreenHandler handler, PlayerInventory inventory, Text title) {
|
||||
super(handler, inventory, title);
|
||||
}
|
||||
|
||||
@Inject(method = "drawBackground", at = @At("HEAD"), cancellable = true)
|
||||
private void replaceDrawBackground(DrawContext context, float deltaTicks, int mouseX, int mouseY, CallbackInfo ci) {
|
||||
final var override = CustomScreenLayouts.getActiveScreenOverride();
|
||||
if (override == null || override.getBackground() == null) return;
|
||||
override.getBackground().renderGeneric(context, this);
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package moe.nea.firmament.mixins.custommodels.screenlayouts;
|
||||
|
||||
|
||||
import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
|
||||
import moe.nea.firmament.features.texturepack.CustomScreenLayouts;
|
||||
import net.minecraft.client.gui.DrawContext;
|
||||
import net.minecraft.client.gui.screen.ingame.InventoryScreen;
|
||||
import net.minecraft.client.gui.screen.ingame.RecipeBookScreen;
|
||||
import net.minecraft.client.gui.screen.recipebook.RecipeBookWidget;
|
||||
import net.minecraft.client.render.RenderLayer;
|
||||
import net.minecraft.entity.player.PlayerInventory;
|
||||
import net.minecraft.screen.PlayerScreenHandler;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.util.Identifier;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
@Mixin(InventoryScreen.class)
|
||||
public abstract class ReplacePlayerBackgrounds extends RecipeBookScreen<PlayerScreenHandler> {
|
||||
public ReplacePlayerBackgrounds(PlayerScreenHandler handler, RecipeBookWidget<?> recipeBook, PlayerInventory inventory, Text title) {
|
||||
super(handler, recipeBook, inventory, title);
|
||||
}
|
||||
|
||||
@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();
|
||||
if (override == null || override.getBackground() == null) return true;
|
||||
override.getBackground().renderGeneric(instance, this);
|
||||
return false;
|
||||
}
|
||||
// TODO: allow moving the player
|
||||
}
|
||||
Reference in New Issue
Block a user