diff options
48 files changed, 1523 insertions, 101 deletions
@@ -6,3 +6,4 @@ versions/*/build/ versions/*/run/ .env .properties +.kotlin diff --git a/build.gradle.kts b/build.gradle.kts index 5feb95d..9bec6d0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,4 +1,5 @@ import com.replaymod.gradle.preprocess.PreprocessExtension +import moe.nea.sharedbuild.MinecraftVersion import moe.nea.sharedbuild.Versions import moe.nea.sharedbuild.parseEnvFile import net.fabricmc.loom.api.LoomGradleExtensionAPI @@ -9,6 +10,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { id("java") kotlin("jvm") + id("com.google.devtools.ksp") id("com.github.johnrengelman.shadow") } @@ -16,7 +18,7 @@ val version = Versions.values().find { it.projectPath == project.path }!! if (version.forgeDep != null) extra.set("loom.platform", "forge") apply(plugin = "gg.essential.loom") -apply(plugin = "com.replaymod.preprocess") +apply(plugin = "dev.deftu.gradle.preprocess") val loom = the<LoomGradleExtensionAPI>() val preprocess = the<PreprocessExtension>() @@ -27,42 +29,20 @@ if (version.needsPack200) { if (version.forgeDep != null) { loom.forge.mixinConfig("mixins.ultranotifier.json") } -val mcJavaVersion = JavaLanguageVersion.of( - when { - version.numericMinecraftVersion >= 12005 -> 21 - version.numericMinecraftVersion >= 11800 -> 17 - version.numericMinecraftVersion >= 11700 -> 16 - else -> 8 - } -) -loom.mixin.defaultRefmapName.set("mixins.ultranotifier.refmap.json") +val mcJavaVersion = version.minecraftVersion.javaLanguageVersion +//loom.mixin.defaultRefmapName.set("mixins.ultranotifier.refmap.json") java.toolchain.languageVersion.set(mcJavaVersion) preprocess.run { - vars.put("MC", version.numericMinecraftVersion) + vars.put("MC", version.minecraftVersion.versionNumber) vars.put("FORGE", if ((version.forgeDep != null)) 1 else 0) + vars.put("FABRIC", if ((version.forgeDep != null)) 0 else 1) vars.put("JAVA", mcJavaVersion.asInt()) } - -repositories { - mavenCentral() - maven("https://maven.minecraftforge.net") { - metadataSources { - artifact() - } - } - maven("https://repo.spongepowered.org/maven/") - maven("https://pkgs.dev.azure.com/djtheredstoner/DevAuth/_packaging/public/maven/v1") - maven("https://jitpack.io") { - content { - includeGroupByRegex("(io|com)\\.github\\..+") - } - } -} - loom.run { this.runs { this.removeIf { it.name != "client" } this.named("client") { + ideConfigGenerated(true) parseEnvFile(file(".env")).forEach { (t, u) -> this.environmentVariable(t, u) } @@ -70,7 +50,7 @@ loom.run { this.property(t, u) } this.property("mixin.debug", "true") - if (version == Versions.MC189) { + if (version.minecraftVersion <= MinecraftVersion.MC11202) { this.programArgs("--tweakClass", "org.spongepowered.asm.launch.MixinTweaker") } } @@ -83,11 +63,14 @@ val shadowImpl by configurations.creating { val shadowModImpl by configurations.creating { modImplementation.extendsFrom(this) } -val include = if (version.forgeDep != null) configurations.getByName("include") else shadowModImpl +val include = if (version.forgeDep != null) configurations.create("includeModImpl") { + configurations.getByName("include").extendsFrom(this) + modImplementation.extendsFrom(this) +} else shadowModImpl val devauthVersion = "1.1.2" dependencies { - "minecraft"("com.mojang:minecraft:" + version.minecraftVersion) - "mappings"(version.mappingDependency) + "minecraft"("com.mojang:minecraft:" + version.minecraftVersion.versionName) + "mappings"(if (version.mappingDependency == "official") loom.officialMojangMappings() else version.mappingDependency) if (version.forgeDep != null) { "forge"(version.forgeDep!!) runtimeOnly("me.djtheredstoner:DevAuth-forge-legacy:$devauthVersion") @@ -96,17 +79,21 @@ dependencies { modImplementation("net.fabricmc.fabric-api:fabric-api:${version.fabricVersion!!}") runtimeOnly("me.djtheredstoner:DevAuth-fabric:$devauthVersion") } + runtimeOnly("com.google.guava:guava:17.0") + implementation(kotlin("reflect")) shadowImpl("com.github.therealbush:eventbus:1.0.2") - if (version.numericMinecraftVersion < 11300) { + include(version.universalCraft) + shadowImpl("io.github.juuxel:libninepatch:1.2.0") + if (version.minecraftVersion.versionNumber < 11300) { shadowImpl("com.mojang:brigadier:1.0.18") } - if (version <= Versions.MC11404F) { + if (version.forgeDep != null) { shadowImpl("org.spongepowered:mixin:0.7.11-SNAPSHOT") { isTransitive = false } annotationProcessor("org.spongepowered:mixin:0.8.5-SNAPSHOT") annotationProcessor("com.google.code.gson:gson:2.10.1") - annotationProcessor("com.google.guava:guava:17.0") + annotationProcessor("com.google.guava:guava:33.4.0-jre") } } @@ -140,7 +127,7 @@ tasks.shadowJar { tasks.processResources { inputs.property("java", mcJavaVersion.asInt().toString()) - inputs.property("mcVersion", version.minecraftVersion) + inputs.property("mcVersion", version.minecraftVersion.versionName) inputs.property("version", project.version.toString()) inputs.property("modName", "Ultra Notifier") inputs.property("description", "Ultra Notifications") @@ -149,7 +136,7 @@ tasks.processResources { } if (version.forgeDep != null) { exclude("fabric.mod.json") - if (version.numericMinecraftVersion < 11400) { + if (version.minecraftVersion < MinecraftVersion.MC1144) { exclude("META-INF/mods.toml") } else { exclude("mcmod.info") @@ -172,11 +159,11 @@ tasks.named("runClient", RunGameTask::class) { }) } -if (version.isBridge) { +if (version == Versions.MC116Forge) { tasks.withType<JavaCompile> { onlyIf { false } } tasks.withType<KotlinCompile> { onlyIf { false } } -} +}
\ No newline at end of file diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index d54cae9..e118815 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -7,4 +7,5 @@ repositories { } dependencies { implementation("com.google.code.gson:gson:2.10.1") -}
\ No newline at end of file + implementation("com.google.guava:guava:33.2.1-jre") +} diff --git a/root.gradle.kts b/root.gradle.kts index d7f638d..94f4f55 100644 --- a/root.gradle.kts +++ b/root.gradle.kts @@ -2,9 +2,10 @@ import com.replaymod.gradle.preprocess.Node import moe.nea.sharedbuild.Versions plugins { - id("com.replaymod.preprocess") version "b09f534" + id("dev.deftu.gradle.preprocess") version "0.7.1" // id("fabric-loom") version "1.6-SNAPSHOT" apply false - kotlin("jvm") version "1.9.23" apply false + kotlin("jvm") version "2.0.20" apply false + id("com.google.devtools.ksp") version "2.0.20-1.0.24" apply false id("gg.essential.loom") version "1.6.+" apply false id("dev.architectury.architectury-pack200") version "0.1.3" id("com.github.johnrengelman.shadow") version "8.1.1" apply false @@ -13,15 +14,40 @@ plugins { allprojects { version = "1.0.0" group = "moe.nea.rxcraft" + repositories { + mavenCentral() + maven("https://maven.minecraftforge.net") { + metadataSources { + artifact() + } + } + maven("https://repo.spongepowered.org/maven/") + maven("https://pkgs.dev.azure.com/djtheredstoner/DevAuth/_packaging/public/maven/v1") + maven("https://jitpack.io") { + content { + includeGroupByRegex("(io|com)\\.github\\..+") + } + } + maven("https://repo.essential.gg/repository/maven-public") + } } preprocess { + strictExtraMappings.set(true) val nodes = mutableMapOf<Versions, Node>() Versions.values().forEach { version -> - nodes[version] = createNode(version.projectName, version.numericMinecraftVersion, version.mappingStyle) + nodes[version] = + createNode(version.projectName, version.minecraftVersion.versionNumber, version.mappingStyle.identifier) } Versions.values().forEach { child -> val parent = child.parent ?: return@forEach - nodes[parent]!!.link(nodes[child]!!, file("versions/mapping-${parent.projectName}-${child.projectName}.txt")) + val mappingFile = file("versions/mapping-${parent.projectName}-${child.projectName}.txt") + if (mappingFile.exists()) { + println("Using mapping file $mappingFile") + nodes[parent]!!.link(nodes[child]!!, mappingFile) + } else { + nodes[parent]!!.link(nodes[child]!!) + println("Skipping mapping file $mappingFile") + } } }
\ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 3f927a5..4a20c6e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -14,6 +14,7 @@ pluginManagement { maven("https://maven.minecraftforge.net/") maven("https://repo.spongepowered.org/maven/") maven("https://repo.sk1er.club/repository/maven-releases/") + maven("https://maven.deftu.dev/releases") } resolutionStrategy { eachPlugin { diff --git a/sharedVariables/src/MappingStyle.kt b/sharedVariables/src/MappingStyle.kt new file mode 100644 index 0000000..deb7fe8 --- /dev/null +++ b/sharedVariables/src/MappingStyle.kt @@ -0,0 +1,6 @@ +package moe.nea.sharedbuild + +enum class MappingStyle(val identifier: String) { + SEARGE("srg"), + YARN("yarn"), +} diff --git a/sharedVariables/src/MinecraftVersion.kt b/sharedVariables/src/MinecraftVersion.kt new file mode 100644 index 0000000..a9e8851 --- /dev/null +++ b/sharedVariables/src/MinecraftVersion.kt @@ -0,0 +1,32 @@ +package moe.nea.sharedbuild + +import org.gradle.jvm.toolchain.JavaLanguageVersion + +enum class MinecraftVersion( + val versionName: String, +) { + MC189("1.8.9"), + MC11200("1.12"), + MC11202("1.12.2"), + MC1144("1.14.4"), + MC11602("1.16.2"), + MC11605("1.16.5"), + MC12006("1.20.6"), + MC121("1.21"), + ; + + val versionNumber = run { + val parts = versionName.split('.').mapTo(mutableListOf()) { it.toInt() } + if (parts.size == 2) parts.add(0) + require(parts.size == 3) + parts[0] * 10000 + parts[1] * 100 + parts[2] + } + val javaVersion: Int = when { + versionNumber >= 12005 -> 21 + versionNumber >= 11800 -> 17 + versionNumber >= 11700 -> 16 + else -> 8 + } + val javaLanguageVersion = JavaLanguageVersion.of(javaVersion) + +} diff --git a/sharedVariables/src/Versions.kt b/sharedVariables/src/Versions.kt index bfccd3a..895ceba 100644 --- a/sharedVariables/src/Versions.kt +++ b/sharedVariables/src/Versions.kt @@ -5,32 +5,61 @@ private fun yarn(version: String): String = enum class Versions( val projectName: String, - val mappingStyle: String, - val minecraftVersion: String, + val minecraftVersion: MinecraftVersion, val mappingDependency: String, - parentName: String?, + val mappingStyle: MappingStyle, val forgeDep: String?, - val needsPack200: Boolean, - val isBridge: Boolean, - val fabricVersion: String? = null, + parentName: String?, ) { - MC189("1.8.9", "srg", "1.8.9", "de.oceanlabs.mcp:mcp_stable:22-1.8.9@zip", "MC11404F", "net.minecraftforge:forge:1.8.9-11.15.1.2318-1.8.9", true, false), - MC11404F("1.14.4-forge", "srg", "1.14.4", "de.oceanlabs.mcp:mcp_stable:58-1.14.4@zip", "MC11404", "net.minecraftforge:forge:1.14.4-28.1.113", false, true), - MC11404("1.14.4", "yarn", "1.14.4", yarn("1.14.4+build.1"), "MC12006", null, false, true, fabricVersion = "0.23.2+1.14"), - MC12006("1.20.6", "yarn", "1.20.6", yarn("1.20.6+build.1"), null, null, false, false, fabricVersion = "0.99.0+1.20.6"), + MC189("1.8.9", + MinecraftVersion.MC189, + "de.oceanlabs.mcp:mcp_stable:22-1.8.9@zip", + MappingStyle.SEARGE, + "net.minecraftforge:forge:1.8.9-11.15.1.2318-1.8.9", + null, + "MC11202"), + MC11202("1.12.2", + MinecraftVersion.MC11202, + "de.oceanlabs.mcp:mcp_stable:39-1.12@zip", + MappingStyle.SEARGE, + "net.minecraftforge:forge:1.12.2-14.23.5.2847", + null, + "MC116Forge"), + MC116Forge("1.16.2-forge", + MinecraftVersion.MC11602, + "official", + MappingStyle.SEARGE, + "net.minecraftforge:forge:1.16.2-33.0.61", + null, + "MC116Fabric"), + MC116Fabric("1.16.2-fabric", + MinecraftVersion.MC11602, + yarn("1.16.2+build.47"), + MappingStyle.YARN, + null, + "0.42.0+1.16", + "MC12006" + ), + MC12006( + "1.20.6", + MinecraftVersion.MC12006, + yarn("1.20.6+build.1"), + MappingStyle.YARN, + null, + fabricVersion = "0.99.0+1.20.6", + null, + ), ; + val platformName = if (forgeDep == null) "fabric" else "forge" + val universalCraft = "gg.essential:universalcraft-${minecraftVersion.versionName}-$platformName:369" + val needsPack200 = forgeDep != null && minecraftVersion <= MinecraftVersion.MC11202 val parent: Versions? by lazy { if (parentName == null) null else Versions.values().find { it.name == parentName }!! } - val numericMinecraftVersion = run { - require(minecraftVersion.count { it == '.' } == 2) - val (a, b, c) = minecraftVersion.split(".").map { it.toInt() } - a * 10000 + b * 100 + c - } val projectPath get() = ":$projectName" companion object { diff --git a/src/main/java/moe/nea/ultranotifier/mixin/AccessorChatHudImpl.java b/src/main/java/moe/nea/ultranotifier/mixin/AccessorChatHudImpl.java new file mode 100644 index 0000000..ec30dcf --- /dev/null +++ b/src/main/java/moe/nea/ultranotifier/mixin/AccessorChatHudImpl.java @@ -0,0 +1,23 @@ +package moe.nea.ultranotifier.mixin; + +import moe.nea.ultranotifier.util.minecrat.AccessorChatHud; +import net.minecraft.client.gui.hud.ChatHud; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(ChatHud.class) +public abstract class AccessorChatHudImpl implements AccessorChatHud { + + //#if MC>1.17 + @Shadow + protected abstract int getLineHeight(); + //#endif + @Override + public int getLineHeight_ultranotifier() { + //#if MC>1.17 + return getLineHeight(); + //#else + //$$return 9; // TODO: better typing here + //#endif + } +} diff --git a/src/main/java/moe/nea/ultranotifier/mixin/AccessorCommandHandler.java b/src/main/java/moe/nea/ultranotifier/mixin/AccessorCommandHandler.java new file mode 100644 index 0000000..9218421 --- /dev/null +++ b/src/main/java/moe/nea/ultranotifier/mixin/AccessorCommandHandler.java @@ -0,0 +1,18 @@ +package moe.nea.ultranotifier.mixin; + +//#if FORGE +//$$import net.minecraft.command.CommandHandler; +//$$import net.minecraft.command.ICommand; +//$$import org.jetbrains.annotations.NotNull; +//$$import org.spongepowered.asm.mixin.Mixin; +//$$import org.spongepowered.asm.mixin.gen.Accessor; +//$$ +//$$import java.util.Set; +//$$ +//$$@Mixin(CommandHandler.class) +//$$public interface AccessorCommandHandler { +//$$ @Accessor("commandSet") +//$$ @NotNull +//$$ Set<@NotNull ICommand> getCommandSet_ultraNotifier(); +//$$} +//#endif
\ No newline at end of file diff --git a/src/main/java/moe/nea/ultranotifier/mixin/ChatHudCategoryTracker.java b/src/main/java/moe/nea/ultranotifier/mixin/ChatHudCategoryTracker.java new file mode 100644 index 0000000..6205506 --- /dev/null +++ b/src/main/java/moe/nea/ultranotifier/mixin/ChatHudCategoryTracker.java @@ -0,0 +1,49 @@ +package moe.nea.ultranotifier.mixin; + +import moe.nea.ultranotifier.datamodel.CategorizedChatLine; +import moe.nea.ultranotifier.datamodel.ChatCategoryArbiter; +import moe.nea.ultranotifier.datamodel.HasCategorizedChatLine; +import net.minecraft.client.gui.hud.ChatHudLine; +import net.minecraft.text.Text; +import org.jetbrains.annotations.NotNull; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +//#if MC> 1.20 +import net.minecraft.client.gui.hud.MessageIndicator; +import net.minecraft.network.message.MessageSignatureData; +//#endif + +@Mixin(ChatHudLine.class) +public class ChatHudCategoryTracker<T> implements HasCategorizedChatLine { + @Unique + CategorizedChatLine categorizedSelf; + + @Inject(method = "<init>", at = @At("RETURN")) + private void onInit( + int creationTick, + //#if MC>1.20 + Text content, + //#elseif MC<1.16 + //$$net.minecraft.util.text.ITextComponent content, + //$$int weird, + //#else + //$$T content, + //$$int weird, + //#endif + //#if MC>1.20 + MessageSignatureData signature, MessageIndicator tag, + //#endif + CallbackInfo ci + ) { + categorizedSelf = ChatCategoryArbiter.INSTANCE.categorize((Text)content); + } + + @Override + public @NotNull CategorizedChatLine getCategorizedChatLine_ultraNotifier() { + return categorizedSelf; + } +} diff --git a/src/main/java/moe/nea/ultranotifier/mixin/ChatHudMessageAdded.java b/src/main/java/moe/nea/ultranotifier/mixin/ChatHudMessageAdded.java index 2b65282..d7ed298 100644 --- a/src/main/java/moe/nea/ultranotifier/mixin/ChatHudMessageAdded.java +++ b/src/main/java/moe/nea/ultranotifier/mixin/ChatHudMessageAdded.java @@ -3,7 +3,7 @@ package moe.nea.ultranotifier.mixin; import moe.nea.ultranotifier.event.ChatGuiLineEvent; import moe.nea.ultranotifier.event.UltraNotifierEvents; import net.minecraft.client.gui.hud.ChatHud; -//#if MC > 11404 +//#if MC >= 1.20 import net.minecraft.client.gui.hud.MessageIndicator; import net.minecraft.network.message.MessageSignatureData; //#endif @@ -18,25 +18,26 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; ) public class ChatHudMessageAdded { @Inject( -//#if MC <= 11404 -//#if FORGE -//$$ method = "printChatMessageWithOptionalDeletion", -//#else -//$$ method = "addMessage(Lnet/minecraft/text/Text;I)V", -//#endif -//#else +//#if MC >= 1.20 method = "addMessage(Lnet/minecraft/text/Text;Lnet/minecraft/network/message/MessageSignatureData;Lnet/minecraft/client/gui/hud/MessageIndicator;)V", +//#elseif MC >= 1.16 +//$$ method = "addMessage(Lnet/minecraft/text/Text;IIZ)V", +//#else +//$$ method = "printChatMessageWithOptionalDeletion", //#endif at = @At("HEAD"), cancellable = true) private void onAddMessage( Text message, -//#if MC <= 11404 -//$$ int chatLineId, -//#else +//#if MC >= 1.20 MessageSignatureData signatureData, MessageIndicator indicator, +//#elseif MC >= 1.16 +//$$ int chatLineId, int timestamp, boolean bl, +//#else +//$$ int chatLineId, //#endif CallbackInfo ci ) { + // TODO: in this method prevent messages from being flushed (to preserve more history) if (UltraNotifierEvents.post(new ChatGuiLineEvent(message)).isCancelled()) { ci.cancel(); } diff --git a/src/main/java/moe/nea/ultranotifier/mixin/ChatScreenTabRenderer.java b/src/main/java/moe/nea/ultranotifier/mixin/ChatScreenTabRenderer.java new file mode 100644 index 0000000..972e40c --- /dev/null +++ b/src/main/java/moe/nea/ultranotifier/mixin/ChatScreenTabRenderer.java @@ -0,0 +1,77 @@ +package moe.nea.ultranotifier.mixin; + +import moe.nea.ultranotifier.gui.ChatUi; +import moe.nea.ultranotifier.util.render.ScreenRenderUtils; +import net.minecraft.client.gui.screen.ChatScreen; +import org.objectweb.asm.Opcodes; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +//#if MC>1.16 +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.util.math.MatrixStack; +//#endif + +@Mixin(ChatScreen.class) +public abstract class ChatScreenTabRenderer { + + private ChatUi chatUi_ultraNotifier = new ChatUi((ChatScreen) (Object) this); + + @Unique + ChatUi chatUi() {return chatUi_ultraNotifier;} + + @Inject( +//#if MC > 1.16 + method = "render", +//#else +//$$ method="drawScreen", +//#endif + at = @At("HEAD")) + private void onRender( +//#if MC >1.20 + DrawContext context, +//#elseif MC > 1.16 +//$$ MatrixStack context, +//#endif + int mouseX, int mouseY, + float delta, + CallbackInfo ci) { + chatUi().renderButtons( + ScreenRenderUtils.umatrix( + //#if MC > 1.16 + context + //#endif + ), + mouseX, mouseY + ); + } + + @Inject( + method = "mouseClicked", + at = @At(value = "INVOKE", + //#if MC > 1.16 + target = "Lnet/minecraft/client/gui/screen/Screen;mouseClicked(DDI)Z", + //#else + //$$target = "Lnet/minecraft/client/gui/GuiScreen;mouseClicked(III)V", + //#endif + opcode = Opcodes.INVOKESPECIAL) + ) + private void onMouseClick( + //#if MC > 1.16 + double mouseX, double mouseY, + //#else + //$$int mouseX, int mouseY, + //#endif + int button, + //#if MC > 1.16 + CallbackInfoReturnable<Boolean> cir + //#else + //$$CallbackInfo cir + //#endif + ) { + chatUi().clickMouse(mouseX, mouseY, button); + } +} diff --git a/src/main/java/moe/nea/ultranotifier/mixin/FilterVisibleMessagePatch.java b/src/main/java/moe/nea/ultranotifier/mixin/FilterVisibleMessagePatch.java new file mode 100644 index 0000000..22e06b5 --- /dev/null +++ b/src/main/java/moe/nea/ultranotifier/mixin/FilterVisibleMessagePatch.java @@ -0,0 +1,72 @@ +package moe.nea.ultranotifier.mixin; + +import moe.nea.ultranotifier.event.UltraNotifierEvents; +import moe.nea.ultranotifier.event.VisibleChatMessageAddedEvent; +import net.minecraft.client.gui.hud.ChatHud; +import net.minecraft.client.gui.hud.ChatHudLine; +import net.minecraft.text.Text; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.ModifyVariable; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +import java.util.Collections; +import java.util.List; + +@Mixin(ChatHud.class) +public class FilterVisibleMessagePatch { +//#if MC > 1.18 + @Inject( + method = "addVisibleMessage", + at = @At("HEAD"), + cancellable = true + ) + private void onAddVisibleMessage(ChatHudLine message, CallbackInfo ci) { + if (UltraNotifierEvents.post(new VisibleChatMessageAddedEvent(message)).isCancelled()) { + ci.cancel(); + } + } +//#else +//$$ @Unique +//$$ ChatHudLine lastAddedChatLine; +//$$ +//$$ @Inject(method = "reset()V", +//$$ at = @At(value = "INVOKE", +//$$ target = "Lnet/minecraft/client/gui/hud/ChatHud;addMessage(Lnet/minecraft/text/Text;IIZ)V"), +//$$ locals = LocalCapture.CAPTURE_FAILHARD) +//$$ private void saveMessageAboutToBeRefreshed(CallbackInfo ci, int i, ChatHudLine chatLine) { +//$$ lastAddedChatLine = chatLine; +//$$ } +//$$ +//$$ @Inject(method = "addMessage(Lnet/minecraft/text/Text;IIZ)V", at = @At(value = "INVOKE", +//#if MC > 1.16 +//$$ target = "Lnet/minecraft/client/util/ChatMessages;breakRenderedChatMessageLines(Lnet/minecraft/text/StringVisitable;ILnet/minecraft/client/font/TextRenderer;)Ljava/util/List;" +//#else +//$$ target = "Lnet/minecraft/client/gui/GuiUtilRenderComponents;splitText(Lnet/minecraft/util/text/ITextComponent;ILnet/minecraft/client/gui/FontRenderer;ZZ)Ljava/util/List;" +//#endif +//$$ )) +//$$ private void saveMessage(Text chatComponent, int chatLineId, int updateCounter, boolean displayOnly, CallbackInfo ci) { +//$$ if (lastAddedChatLine != null && lastAddedChatLine.getText() != chatComponent) { +//$$ throw new RuntimeException("Out of order message received"); +//$$ } +//$$ if (lastAddedChatLine == null) +//$$ lastAddedChatLine = new ChatHudLine(chatLineId, chatComponent, updateCounter); +//$$ } +//$$ +//$$ @ModifyVariable( +//$$ method = "addMessage(Lnet/minecraft/text/Text;IIZ)V", +//$$ at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/hud/ChatHud;isChatFocused()Z") +//$$ ) +//$$ private List onAddVisibleMessage(List value) { +//$$ VisibleChatMessageAddedEvent event = new VisibleChatMessageAddedEvent(lastAddedChatLine); +//$$ lastAddedChatLine = null; +//$$ if (UltraNotifierEvents.post(event).isCancelled()) { +//$$ return Collections.emptyList(); +//$$ } +//$$ return value; +//$$ } +//#endif +} diff --git a/src/main/kotlin/AllModules.kt b/src/main/kotlin/AllModules.kt new file mode 100644 index 0000000..01cfb36 --- /dev/null +++ b/src/main/kotlin/AllModules.kt @@ -0,0 +1,17 @@ +package moe.nea.ultranotifier + +import moe.nea.ultranotifier.commands.Commands +import moe.nea.ultranotifier.datamodel.ChatCategoryArbiter +import moe.nea.ultranotifier.event.SubscriptionTarget +import moe.nea.ultranotifier.event.TickEvent +import moe.nea.ultranotifier.gui.ScreenUtil + +object AllModules { + val allModules: List<SubscriptionTarget> = listOf( + ChatStore, + Commands, + ScreenUtil, + TickEvent, + ChatCategoryArbiter, + ) +} diff --git a/src/main/kotlin/ChatStore.kt b/src/main/kotlin/ChatStore.kt new file mode 100644 index 0000000..4cc6ee1 --- /dev/null +++ b/src/main/kotlin/ChatStore.kt @@ -0,0 +1,53 @@ +package moe.nea.ultranotifier + +import com.mojang.brigadier.builder.LiteralArgumentBuilder +import moe.nea.ultranotifier.commands.UltraCommandSource +import moe.nea.ultranotifier.commands.literalText +import moe.nea.ultranotifier.event.ChatGuiLineEvent +import moe.nea.ultranotifier.event.CommandRegistrationEvent +import moe.nea.ultranotifier.event.PacketChatLineEvent +import moe.nea.ultranotifier.event.SubscriptionTarget +import moe.nea.ultranotifier.event.UltraSubscribe +import moe.nea.ultranotifier.gui.MessageUi +import moe.nea.ultranotifier.gui.ScreenUtil +import moe.nea.ultranotifier.util.IdentityCharacteristics +import net.minecraft.text.Text + +object ChatStore : SubscriptionTarget { + + data class ChatLine( + val text: Text, + var fromPacket: Boolean = false, + var isDisplayed: Boolean = false, + ) + + val allLines = object : LinkedHashMap<IdentityCharacteristics<Text>, ChatLine>() { + override fun removeEldestEntry(eldest: MutableMap.MutableEntry<IdentityCharacteristics<Text>, ChatLine>?): Boolean { + return size > 500 // TODO: config + } + } + + fun insertChatLine(text: Text): ChatLine { + return allLines.getOrPut(IdentityCharacteristics(text)) { ChatLine(text) } + } + + @UltraSubscribe + fun onMessageDisplayed(event: ChatGuiLineEvent) { + insertChatLine(event.component).isDisplayed = true + } + + @UltraSubscribe + fun registerCommands(event: CommandRegistrationEvent) { + event.dispatcher.register(LiteralArgumentBuilder.literal<UltraCommandSource?>("ultranotifier") + .executes { + it.source.sendFeedback(literalText("Opening screen")) + ScreenUtil.openScreen = (MessageUi()) + 0 + }) + } + + @UltraSubscribe + fun onMessageReceived(event: PacketChatLineEvent) { + insertChatLine(event.component).fromPacket = true + } +} diff --git a/src/main/kotlin/Constants.kt b/src/main/kotlin/Constants.kt index a1903ce..46d4236 100644 --- a/src/main/kotlin/Constants.kt +++ b/src/main/kotlin/Constants.kt @@ -1,6 +1,6 @@ package moe.nea.ultranotifier - +// TODO: blossom this shit object Constants { const val MOD_ID = "ultranotifier" const val VERSION = "1.0.0" @@ -23,5 +23,15 @@ object Constants { "1.20.6" //#elseif MC == 11404 //$$ "1.14.4" +//#elseif MC == 11202 +//$$ "1.12.2" +//#elseif MC == 11605 +//$$ "1.16.5" +//#elseif MC == 11602 +//$$ "1.16.2" +//#elseif MC == 12100 +//$$ "1.21" +//#elseif MC == 12101 +//$$ "1.21.1" //#endif } diff --git a/src/main/kotlin/UltraNotifier.kt b/src/main/kotlin/UltraNotifier.kt index dce1fda..ddf8e87 100644 --- a/src/main/kotlin/UltraNotifier.kt +++ b/src/main/kotlin/UltraNotifier.kt @@ -1,12 +1,14 @@ package moe.nea.ultranotifier -import moe.nea.ultranotifier.commands.Commands +import moe.nea.ultranotifier.event.RegistrationFinishedEvent +import moe.nea.ultranotifier.event.UltraEvent +import moe.nea.ultranotifier.event.UltraNotifierEvents import moe.nea.ultranotifier.init.NeaMixinConfig import java.io.File object UltraNotifier { val logger = -//#if MC <= 11404 +//#if MC <= 1.17 //$$ org.apache.logging.log4j.LogManager.getLogger("UltraNotifier")!! //#else org.slf4j.LoggerFactory.getLogger("UltraNotifier")!! @@ -17,7 +19,15 @@ object UltraNotifier { for (mixinPlugin in NeaMixinConfig.getMixinPlugins()) { logger.info("Loaded ${mixinPlugin.mixins.size} mixins for ${mixinPlugin.mixinPackage}.") } - Commands.init() + logger.info("All modules: ${AllModules.allModules}") + AllModules.allModules.forEach { + logger.info("Registering $it") + UltraNotifierEvents.register(it) + it.init() + } + + RegistrationFinishedEvent().post() + } val configFolder = File("config/ultra-notifier").also { diff --git a/src/main/kotlin/UltraNotifierEntryPoint.kt b/src/main/kotlin/UltraNotifierEntryPoint.kt index 42ae064..aa84dc5 100644 --- a/src/main/kotlin/UltraNotifierEntryPoint.kt +++ b/src/main/kotlin/UltraNotifierEntryPoint.kt @@ -3,14 +3,14 @@ package moe.nea.ultranotifier //#if FORGE //$$import net.minecraftforge.fml.common.Mod //$$ -//#if MC == 10809 +//#if MC < 1.13 //$$import net.minecraftforge.fml.common.event.FMLInitializationEvent //$$@Mod(modid = Constants.MOD_ID, version = Constants.VERSION, useMetadata = true) //#else //$$@Mod(Constants.MOD_ID) //#endif //$$class UltraNotifierEntryPoint { -//#if MC == 10809 +//#if MC < 1.13 //$$ @Mod.EventHandler //$$ fun onInit(@Suppress("UNUSED_PARAMETER") event: FMLInitializationEvent) { //#else diff --git a/src/main/kotlin/commands/BrigadierPatchbay.kt b/src/main/kotlin/commands/BrigadierPatchbay.kt new file mode 100644 index 0000000..b88d0e7 --- /dev/null +++ b/src/main/kotlin/commands/BrigadierPatchbay.kt @@ -0,0 +1,119 @@ +package moe.nea.ultranotifier.commands + +//#if MC < 1.16 +//$$import com.mojang.brigadier.CommandDispatcher +//$$import com.mojang.brigadier.builder.LiteralArgumentBuilder +//$$import com.mojang.brigadier.tree.CommandNode +//$$import moe.nea.ultranotifier.event.CommandRegistrationEvent +//$$import moe.nea.ultranotifier.event.RegistrationFinishedEvent +//$$import moe.nea.ultranotifier.event.UltraNotifierEvents +//$$import moe.nea.ultranotifier.event.UltraSubscribe +//$$import moe.nea.ultranotifier.event.SubscriptionTarget +//$$import moe.nea.ultranotifier.mixin.AccessorCommandHandler +//$$import net.minecraft.command.CommandBase +//$$import net.minecraft.command.CommandHandler +//$$import net.minecraft.command.ICommandSender +//$$import net.minecraft.server.MinecraftServer +//$$import net.minecraft.util.text.ITextComponent +//$$import net.minecraftforge.client.ClientCommandHandler +//$$ +//$$fun CommandHandler.getCommandSet() = (this as AccessorCommandHandler).commandSet_ultraNotifier +//$$ +//$$class BridgedCommandSource( +//$$ val sender: ICommandSender +//$$) : UltraCommandSource { +//$$ override fun sendFeedback(text: ITextComponent) { +//$$ sender.sendMessage(text) +//$$ } +//$$} +//$$ +//$$class BrigadierCommand( +//$$ val dispatcher: CommandDispatcher<UltraCommandSource>, +//$$ val node: CommandNode<UltraCommandSource> +//$$) : CommandBase() { +//#if MC >= 1.12 +//$$ override fun checkPermission(server: MinecraftServer, sender: ICommandSender): Boolean { +//$$ return true +//$$ } +//#else +//$$ override fun canCommandSenderUseCommand(sender: ICommandSender): Boolean { +//$$ return true +//$$ } +//#endif +//$$ +//$$ override fun getName(): String { +//$$ return node.name +//$$ } +//$$ +//$$ override fun getUsage(sender: ICommandSender): String { +//$$ return "" +//$$ } +//$$ +//$$ private fun getCommandLineText(args: Array<out String>) = "${node.name} ${args.joinToString(" ")}".trim() +//$$ +//$$ +//#if MC < 1.12 +//$$ override fun processCommand(sender: ICommandSender, args: Array<out String>) { +//#else +//$$ override fun execute(server: MinecraftServer, sender: ICommandSender, args: Array<out String>) { +//#endif +//$$ val source = BridgedCommandSource(sender) +//$$ val results = dispatcher.parse(getCommandLineText(args), source) +//$$ kotlin.runCatching { +//$$ dispatcher.execute(results) +//$$ Unit +//$$ }.recoverCatching { +//$$ source.sendFeedback(literalText("Could not execute ultra command: ${it.message}")) +//$$ } +//$$ } +//$$} +//$$ +//$$object BrigadierPatchbay : SubscriptionTarget { +//$$ +//$$ @UltraSubscribe +//$$ fun onAfterRegistration(event: RegistrationFinishedEvent) { +//$$ fullReload() +//$$ } +//$$ +//$$ @UltraSubscribe +//$$ fun onCommands(event: CommandRegistrationEvent) { +//$$ event.dispatcher +//$$ .register(LiteralArgumentBuilder.literal<UltraCommandSource>("reloadcommands") +//$$ .executes { +//$$ it.source.sendFeedback(literalText("Reloading commands")) +//$$ fullReload() +//$$ it.source.sendFeedback(literalText("Reload completed")) +//$$ 0 +//$$ }) +//$$ } +//$$ +//$$ fun fullReload() { +//$$ val handler = ClientCommandHandler.instance +//$$ unpatch(handler) +//$$ val dispatcher = createDispatcher() +//$$ UltraNotifierEvents.post(CommandRegistrationEvent(dispatcher)) +//$$ patch(handler, dispatcher) +//$$ } +//$$ +//$$ fun createDispatcher() = CommandDispatcher<UltraCommandSource>() +//$$ +//$$ fun unpatch(handler: CommandHandler) { +//$$ handler.getCommandSet() +//$$ .removeIf { +//$$ it is BrigadierCommand +//$$ } +//$$ handler.commands.entries +//$$ .removeIf { +//$$ it.value is BrigadierCommand +//$$ } +//$$ } +//$$ +//$$ fun patch(handler: CommandHandler, dispatcher: CommandDispatcher<UltraCommandSource>) { +//$$ dispatcher.root.children +//$$ .map { BrigadierCommand(dispatcher, it) } +//$$ .forEach(handler::registerCommand) +//$$ } +//$$} +//#endif + + diff --git a/src/main/kotlin/commands/Commands.kt b/src/main/kotlin/commands/Commands.kt index 75047cd..5975bb2 100644 --- a/src/main/kotlin/commands/Commands.kt +++ b/src/main/kotlin/commands/Commands.kt @@ -1,9 +1,11 @@ package moe.nea.ultranotifier.commands -import com.mojang.brigadier.CommandDispatcher import com.mojang.brigadier.builder.LiteralArgumentBuilder import moe.nea.ultranotifier.UltraNotifier +import moe.nea.ultranotifier.event.CommandRegistrationEvent +import moe.nea.ultranotifier.event.SubscriptionTarget import moe.nea.ultranotifier.event.UltraNotifierEvents +import moe.nea.ultranotifier.event.UltraSubscribe import net.minecraft.text.Text interface CustomSource { @@ -14,35 +16,50 @@ interface CustomSource { typealias UltraCommandSource = //#if FORGE //$$ CustomSource -//#else +//#elseif MC > 1.18 net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource - +//#else +//$$ net.fabricmc.fabric.api.client.command.v1.FabricClientCommandSource //#endif +fun translatableText(key: String, vararg args: String) = +//#if MC > 1.17 + Text.translatable(key, *args) +//#else +//$$ net.minecraft.text.TranslatableText(key, *args) +//#endif + fun literalText(string: String): Text = -//#if MC >= 11400 +//#if MC > 1.17 Text.literal(string) //#else -//$$ net.minecraft.util.ChatComponentText(string) +//$$ net.minecraft.text.LiteralText(string) //#endif -object Commands { - fun registerAll(dispatcher: CommandDispatcher<UltraCommandSource>) { - dispatcher.register(LiteralArgumentBuilder.literal<UltraCommandSource>("hello") - .executes { - it.source.sendFeedback(literalText("Hello World")) - 0 - }) +object Commands : SubscriptionTarget { + @UltraSubscribe + fun registerTestCommand(event: CommandRegistrationEvent) { + event.dispatcher.register(LiteralArgumentBuilder.literal<UltraCommandSource>("hello") + .executes { + it.source.sendFeedback(literalText("Hello World")) + 0 + }) } - fun init() { - UltraNotifierEvents.register(this) +//#if MC <= 1.18 && FABRIC +//$$ @UltraSubscribe +//$$ fun registerEverythingOnce(event: moe.nea.ultranotifier.event.RegistrationFinishedEvent) { +//$$ CommandRegistrationEvent(net.fabricmc.fabric.api.client.command.v1.ClientCommandManager.DISPATCHER).post() +//$$ } +//#endif + + override fun init() { //#if FORGE -//$$ -//#else +//$$ UltraNotifierEvents.register(BrigadierPatchbay) +//#elseif MC > 1.18 net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback.EVENT.register { dispatcher, registryAccess -> - registerAll(dispatcher) + UltraNotifierEvents.post(CommandRegistrationEvent(dispatcher)) } //#endif UltraNotifier.logger.info("Initialized command subsystem") diff --git a/src/main/kotlin/datamodel/ChatType.kt b/src/main/kotlin/datamodel/ChatType.kt new file mode 100644 index 0000000..47abe2a --- /dev/null +++ b/src/main/kotlin/datamodel/ChatType.kt @@ -0,0 +1,175 @@ +package moe.nea.ultranotifier.datamodel + +import moe.nea.ultranotifier.UltraNotifier +import moe.nea.ultranotifier.event.SubscriptionTarget +import moe.nea.ultranotifier.event.TickEvent +import moe.nea.ultranotifier.event.UltraSubscribe +import moe.nea.ultranotifier.event.VisibleChatMessageAddedEvent +import moe.nea.ultranotifier.util.GsonUtil +import moe.nea.ultranotifier.util.duplicatesBy +import moe.nea.ultranotifier.util.minecrat.MC +import moe.nea.ultranotifier.util.minecrat.category +import moe.nea.ultranotifier.util.minecrat.getFormattedTextCompat +import moe.nea.ultranotifier.util.minecrat.removeFormattingCodes +import net.minecraft.text.Text +import util.KSerializable +import java.util.function.Predicate +import java.util.regex.Pattern + +data class ChatTypeId( + val id: String +) + +@KSerializable +data class ChatType( + val name: String, + val patterns: List<ChatPattern>, +) + +data class ChatPattern( + val text: String +) { + val pattern = Pattern.compile(text) + val predicate: Predicate<String> = +//#if JAVA > 11 + pattern.asMatchPredicate() +//#else +//$$ Predicate { it: String -> pattern.matcher(it).matches() } +//#endif +} + +data class CategoryId(val id: String) + +@KSerializable +data class ChatCategory( + val id: CategoryId, + val label: String, + val chatTypes: Set<ChatTypeId>, +) + +data class ChatUniverse( + val name: String, + val types: Map<ChatTypeId, ChatType>, + val categories: List<ChatCategory>, +) { + fun categorize( + text: String + ): CategorizedChatLine { + val types = this.types + .asSequence() + .filter { + it.value.patterns.any { + it.predicate.test(text) + } + } + .map { + it.key + } + .toSet() + return CategorizedChatLine( + text, types + ) + } +} + +data class CategorizedChatLine( + val text: String, + val types: Set<ChatTypeId>, +// val categories: Set<ChatCategory>, +) + +@KSerializable +data class UniverseMeta( + // TODO: implement the ip filter + val ipFilter: List<ChatPattern> = listOf(), + val name: String, +) + +interface HasCategorizedChatLine { + val categorizedChatLine_ultraNotifier: CategorizedChatLine +} + +data class UniverseId( + val id: String +) + +private fun loadAllUniverses(): Map<UniverseId, ChatUniverse> = buildMap { + for (file in UltraNotifier.configFolder + .resolve("universes/") + .listFiles() ?: emptyArray()) { + runCatching { + val meta = GsonUtil.read<UniverseMeta>(file.resolve("meta.json")) + val types = GsonUtil.read<Map<ChatTypeId, ChatType>>(file.resolve("chattypes.json")) + val categories = GsonUtil.read<List<ChatCategory>>(file.resolve("categories.json")) + // Validate categories linking properly + for (category in categories) { + for (chatType in category.chatTypes) { + if (chatType in types.keys) { + UltraNotifier.logger.warn("Missing definition for $chatType required by ${category.id} in $file") + } + } + } + for (category in categories.asSequence().duplicatesBy { it.id }) { + UltraNotifier.logger.warn("Found duplicate category ${category.id} in $file") + } + + put( + UniverseId(file.name), + ChatUniverse( + meta.name, + types, + categories, + )) + }.getOrElse { + UltraNotifier.logger.warn("Could not load universe at $file", it) + } + } +} + +object ChatCategoryArbiter : SubscriptionTarget { + val specialAll = CategoryId("special-all") + + var allUniverses = loadAllUniverses() + + var activeUniverse: ChatUniverse? = allUniverses.values.single() + private val allCategoryList = listOf( + ChatCategory(specialAll, "All", setOf()) + ) + + val categories // TODO: memoize + get() = (activeUniverse?.categories ?: listOf()) + allCategoryList + + var selectedCategoryId = specialAll + set(value) { + field = value + selectedCategory = findCategory(value) + } + private var lastSelectedId = selectedCategoryId + var selectedCategory: ChatCategory = findCategory(selectedCategoryId) + private set + + @UltraSubscribe + fun onTick(event: TickEvent) { + if (lastSelectedId != selectedCategoryId) { + MC.chatHud.reset() + lastSelectedId = selectedCategoryId + } + } + + @UltraSubscribe + fun onVisibleChatMessage(event: VisibleChatMessageAddedEvent) { + val cl = event.chatLine.category + if (selectedCategory.id == specialAll) + return + if (cl.types.none { it in selectedCategory.chatTypes }) + event.cancel() + } + + fun findCategory(id: CategoryId) = categories.find { it.id == id }!! + + fun categorize(content: Text): CategorizedChatLine { + val stringContent = content.getFormattedTextCompat().removeFormattingCodes() + return activeUniverse?.categorize(stringContent) ?: CategorizedChatLine(stringContent, setOf()) + } +} + diff --git a/src/main/kotlin/event/ChatGuiLineEvent.kt b/src/main/kotlin/event/ChatGuiLineEvent.kt index e37d31f..eb585dd 100644 --- a/src/main/kotlin/event/ChatGuiLineEvent.kt +++ b/src/main/kotlin/event/ChatGuiLineEvent.kt @@ -3,7 +3,13 @@ package moe.nea.ultranotifier.event import net.minecraft.text.Text class ChatGuiLineEvent(val component: Text) : UltraEvent() { - val string = component.string + val string = +//#if MC < 1.16 +//$$ component.unformattedText // Why does remap not do this automatically? hello? +//#else + component.string +//#endif + override fun toString(): String { return "ChatLineAddedEvent($string)" } diff --git a/src/main/kotlin/event/CommandRegistrationEvent.kt b/src/main/kotlin/event/CommandRegistrationEvent.kt new file mode 100644 index 0000000..49bc637 --- /dev/null +++ b/src/main/kotlin/event/CommandRegistrationEvent.kt @@ -0,0 +1,10 @@ +package moe.nea.ultranotifier.event + +import com.mojang.brigadier.CommandDispatcher +import moe.nea.ultranotifier.commands.UltraCommandSource + +/** + * Fired whenever commands need to be registered. This may be multiple times during each launch. Old commands will be + * automatically unregistered first. + */ +class CommandRegistrationEvent(val dispatcher: CommandDispatcher<UltraCommandSource>) : UltraEvent() diff --git a/src/main/kotlin/event/RegistrationFinishedEvent.kt b/src/main/kotlin/event/RegistrationFinishedEvent.kt new file mode 100644 index 0000000..325e1d8 --- /dev/null +++ b/src/main/kotlin/event/RegistrationFinishedEvent.kt @@ -0,0 +1,6 @@ +package moe.nea.ultranotifier.event + +/** + * Indicates that the registration of all ultra event handlers is done + */ +class RegistrationFinishedEvent : UltraEvent() diff --git a/src/main/kotlin/event/SubscriptionTarget.kt b/src/main/kotlin/event/SubscriptionTarget.kt new file mode 100644 index 0000000..e66eb94 --- /dev/null +++ b/src/main/kotlin/event/SubscriptionTarget.kt @@ -0,0 +1,5 @@ +package moe.nea.ultranotifier.event + +interface SubscriptionTarget { + fun init() = Unit +} diff --git a/src/main/kotlin/event/TickEvent.kt b/src/main/kotlin/event/TickEvent.kt new file mode 100644 index 0000000..4bd2c6c --- /dev/null +++ b/src/main/kotlin/event/TickEvent.kt @@ -0,0 +1,26 @@ +package moe.nea.ultranotifier.event + +//#if FABRIC +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents +//#endif + +class TickEvent : UltraEvent(), UltraEvent.Silent<TickEvent> { + + companion object : SubscriptionTarget { + override fun init() { +//#if FABRIC + ClientTickEvents.END_CLIENT_TICK.register(ClientTickEvents.EndTick { + TickEvent().post() + }) +//#else +//$$ net.minecraftforge.common.MinecraftForge.EVENT_BUS.register(object { +//$$ @UltraSubscribe +//$$ fun onForgeEvent(event: net.minecraftforge.event.TickEvent.ClientTickEvent) { +//$$ if (event.phase == net.minecraftforge.event.TickEvent.Phase.END) +//$$ TickEvent().post() +//$$ } +//$$ }) +//#endif + } + } +} diff --git a/src/main/kotlin/event/UltraEvent.kt b/src/main/kotlin/event/UltraEvent.kt index 42fa4f2..80f63fc 100644 --- a/src/main/kotlin/event/UltraEvent.kt +++ b/src/main/kotlin/event/UltraEvent.kt @@ -7,6 +7,8 @@ abstract class UltraEvent : me.bush.eventbus.event.Event() //#endif { + interface Silent<T> where T : Silent<T>, T : UltraEvent + //#if FORGE //$$ override fun isCancelable(): Boolean { //$$ return this.isCancellable() @@ -32,6 +34,10 @@ abstract class UltraEvent : setCancelled(true) } + fun post() { + UltraNotifierEvents.post(this) + } + } diff --git a/src/main/kotlin/event/UltraNotifierEvents.kt b/src/main/kotlin/event/UltraNotifierEvents.kt index 34d1769..9c1e1ee 100644 --- a/src/main/kotlin/event/UltraNotifierEvents.kt +++ b/src/main/kotlin/event/UltraNotifierEvents.kt @@ -9,14 +9,17 @@ object UltraNotifierEvents { //#else me.bush.eventbus.bus.EventBus { UltraNotifier.logger.warn("EventBus: $it") } //#endif + @JvmStatic fun <T : UltraEvent> post(event: T): T { - UltraNotifier.logger.info("Posting $event") + if (event !is UltraEvent.Silent<*>) { + UltraNotifier.logger.info("Posting $event") + } eventBus.post(event) return event } - fun register(obj: Any) { + fun register(obj: SubscriptionTarget) { //#if FORGE //$$ eventBus.register(obj) //#else diff --git a/src/main/kotlin/event/VisibleChatMessageAddedEvent.kt b/src/main/kotlin/event/VisibleChatMessageAddedEvent.kt new file mode 100644 index 0000000..97d919b --- /dev/null +++ b/src/main/kotlin/event/VisibleChatMessageAddedEvent.kt @@ -0,0 +1,17 @@ +package moe.nea.ultranotifier.event + +import net.minecraft.client.gui.hud.ChatHudLine +import net.minecraft.text.Text + +typealias ChattyHudLine = + ChatHudLine +//#if MC < 1.20 +//#if MC > 1.16 +//$$ <Text> +//#endif +//#endif + + +data class VisibleChatMessageAddedEvent( + val chatLine: ChattyHudLine, +) : UltraEvent() diff --git a/src/main/kotlin/gui/ChatUi.kt b/src/main/kotlin/gui/ChatUi.kt new file mode 100644 index 0000000..b812b73 --- /dev/null +++ b/src/main/kotlin/gui/ChatUi.kt @@ -0,0 +1,91 @@ +package moe.nea.ultranotifier.gui + +import gg.essential.universal.UGraphics +import gg.essential.universal.UMatrixStack +import moe.nea.ultranotifier.datamodel.ChatCategory +import moe.nea.ultranotifier.datamodel.ChatCategoryArbiter +import moe.nea.ultranotifier.util.minecrat.MC +import moe.nea.ultranotifier.util.minecrat.accessor +import moe.nea.ultranotifier.util.render.ScreenRenderUtils +import net.minecraft.client.gui.screen.ChatScreen +import java.awt.Color + +class ChatUi(val chatScreen: ChatScreen) { + + val Double.value get() = this + val Float.value get() = this + fun getChatBgOpacity(opacityMultiplier: Double = 1.0): Color { + return Color((MC.instance.options.textBackgroundOpacity.value * opacityMultiplier * 255).toInt() shl 24, true) + } + + fun calculateChatTop(): Double { + val ch = MC.chatHud + ch.accessor() + val chatOffset = + 40 + val chatTop = + (chatScreen.height - chatOffset) / ch.chatScale - ch.visibleLineCount * ch.lineHeight_ultranotifier + return chatTop.toDouble() + } + + fun iterateButtons( + onEach: ( + label: ChatCategory, + isSelected: Boolean, + pos: Rect, + ) -> Unit + ) { + val chatTop = calculateChatTop() + var xOffset = 5 + val top = chatTop - 16.0 + for (button in ChatCategoryArbiter.categories) { + val w = ScreenRenderUtils.getTextWidth(button.label) + 3 + val isSelected = button == ChatCategoryArbiter.selectedCategory + onEach(button, isSelected, Rect(xOffset.toDouble(), top, w.toDouble(), 16.0)) + xOffset += w + 5 + } + } + + data class Rect( + val x: Double, val y: Double, + val w: Double, val h: Double, + ) { + fun contains(mouseX: Double, mouseY: Double): Boolean { + return mouseX in (l..<r) && mouseY in (t..<b) + } + + val l get() = x + val t get() = y + val r get() = x + w + val b get() = y + h + val cy get() = y + h / 2 + val cx get() = x + w / 2 + } + + fun renderButtons( + matrixStack: UMatrixStack, + mouseX: Double, mouseY: Double, + ) { + iterateButtons { button, isSelected, pos -> + UGraphics.enableBlend() + ScreenRenderUtils.fillRect(matrixStack, + pos.l, pos.t, pos.r, pos.b, + if (isSelected) getChatBgOpacity() else getChatBgOpacity(0.75)) + UGraphics.disableBlend() + ScreenRenderUtils.renderText(matrixStack, + pos.l + 2, pos.cy - 9 / 2, + if (isSelected) "§a${button.label}" else "§f${button.label}") + } + } + + fun clickMouse(mouseX: Double, mouseY: Double, button: Int) { + iterateButtons { label, isSelected, pos -> + if (pos.contains(mouseX, mouseY)) { + if (button == 0) { + ChatCategoryArbiter.selectedCategoryId = label.id + } + // TODO: right click options or something + } + } + } +} diff --git a/src/main/kotlin/gui/MessageUi.kt b/src/main/kotlin/gui/MessageUi.kt new file mode 100644 index 0000000..5f337f3 --- /dev/null +++ b/src/main/kotlin/gui/MessageUi.kt @@ -0,0 +1,28 @@ +package moe.nea.ultranotifier.gui + +import gg.essential.universal.UMatrixStack +import gg.essential.universal.UScreen +import juuxel.libninepatch.NinePatch +import moe.nea.ultranotifier.util.render.ScreenRenderUtils +import moe.nea.ultranotifier.util.ultraIdentifier +import java.awt.Color + +class MessageUi : UScreen() { + override fun onDrawScreen(matrixStack: UMatrixStack, mouseX: Int, mouseY: Int, partialTicks: Float) { + super.onDrawScreen(matrixStack, mouseX, mouseY, partialTicks) + ScreenRenderUtils.fillRect(matrixStack, 0.0, 0.0, width.toDouble(), height.toDouble(), Color.RED) + ScreenRenderUtils.renderTexture( + ultraIdentifier("textures/gui/square_panel.png"), + matrixStack, + 200.0, 0.0, 300.0, 100.0 + ) + ScreenRenderUtils.renderNineSlice( + NinePatch.builder(ultraIdentifier("textures/gui/square_panel.png")) + .cornerSize(10) + .mode(NinePatch.Mode.STRETCHING) + .cornerUv(0.1F, 0.1F).build(), + matrixStack, + 225.0, 25.0, 275.0, 75.0 + ) + } +} diff --git a/src/main/kotlin/gui/ScreenUtil.kt b/src/main/kotlin/gui/ScreenUtil.kt new file mode 100644 index 0000000..bb3dfc3 --- /dev/null +++ b/src/main/kotlin/gui/ScreenUtil.kt @@ -0,0 +1,19 @@ +package moe.nea.ultranotifier.gui + +import gg.essential.universal.UScreen +import moe.nea.ultranotifier.event.SubscriptionTarget +import moe.nea.ultranotifier.event.TickEvent +import moe.nea.ultranotifier.event.UltraSubscribe +import net.minecraft.client.gui.screen.Screen + +object ScreenUtil : SubscriptionTarget { + var openScreen: Screen? = null + + @UltraSubscribe + fun onTick(event: TickEvent) { + openScreen?.let { + UScreen.displayScreen(it) + openScreen = null + } + } +} diff --git a/src/main/kotlin/util/GsonUtil.kt b/src/main/kotlin/util/GsonUtil.kt new file mode 100644 index 0000000..d689330 --- /dev/null +++ b/src/main/kotlin/util/GsonUtil.kt @@ -0,0 +1,55 @@ +package moe.nea.ultranotifier.util + +import com.google.gson.GsonBuilder +import com.google.gson.TypeAdapter +import com.google.gson.reflect.TypeToken +import com.google.gson.stream.JsonReader +import com.google.gson.stream.JsonWriter +import moe.nea.ultranotifier.datamodel.CategoryId +import moe.nea.ultranotifier.datamodel.ChatPattern +import moe.nea.ultranotifier.datamodel.ChatTypeId +import net.minecraft.util.Identifier +import util.KotlinTypeAdapterFactory +import java.io.File + +object GsonUtil { + val sharedGsonBuilder = GsonBuilder() + .registerTypeAdapterFactory(KotlinTypeAdapterFactory()) + .registerTypeHierarchyAdapter(Identifier::class.java, object : TypeAdapter<Identifier>() { + override fun write(out: JsonWriter, value: Identifier) { + out.value(value.namespace + ":" + value.path) + } + + override fun read(`in`: JsonReader): Identifier { + val identifierName = `in`.nextString() + val parts = identifierName.split(":") + require(parts.size != 2) { "$identifierName is not a valid identifier" } + return identifier(parts[0], parts[1]) + } + }.nullSafe()) + .registerTypeHierarchyAdapter(ChatPattern::class.java, stringWrapperAdapter(ChatPattern::text, ::ChatPattern)) + .registerTypeHierarchyAdapter(CategoryId::class.java, stringWrapperAdapter(CategoryId::id, ::CategoryId)) + .registerTypeHierarchyAdapter(ChatTypeId::class.java, stringWrapperAdapter(ChatTypeId::id, ::ChatTypeId)) + + private fun <T> stringWrapperAdapter(from: (T) -> String, to: (String) -> T): TypeAdapter<T> { + return object : TypeAdapter<T>() { + override fun write(out: JsonWriter, value: T) { + out.value(from(value)) + } + + override fun read(`in`: JsonReader): T { + return to(`in`.nextString()) + } + }.nullSafe() + } + + inline fun <reified T : Any> read(meta: File): T { + // TODO: add exception + meta.reader().use { reader -> + return gson.fromJson(reader, object : TypeToken<T>() {}.type) + } + } + + val gson = sharedGsonBuilder.create() + val prettyGson = sharedGsonBuilder.setPrettyPrinting().create() +} diff --git a/src/main/kotlin/util/IdentityCharacteristics.kt b/src/main/kotlin/util/IdentityCharacteristics.kt new file mode 100644 index 0000000..d3f5294 --- /dev/null +++ b/src/main/kotlin/util/IdentityCharacteristics.kt @@ -0,0 +1,15 @@ +package moe.nea.ultranotifier.util + +class IdentityCharacteristics<T>(val value: T) { + override fun hashCode(): Int { + return System.identityHashCode(value) + } + + override fun equals(other: Any?): Boolean { + return value === other + } + + override fun toString(): String { + return "IdentityCharacteristics($value)" + } +} diff --git a/src/main/kotlin/util/KSerializable.kt b/src/main/kotlin/util/KSerializable.kt new file mode 100644 index 0000000..ef4c953 --- /dev/null +++ b/src/main/kotlin/util/KSerializable.kt @@ -0,0 +1,112 @@ +package util +import com.google.gson.* +import com.google.gson.annotations.SerializedName +import com.google.gson.reflect.TypeToken +import com.google.gson.stream.JsonReader +import com.google.gson.stream.JsonToken +import com.google.gson.stream.JsonWriter +import kotlin.reflect.* +import kotlin.reflect.full.findAnnotation +import kotlin.reflect.full.isSubtypeOf +import kotlin.reflect.full.memberProperties +import kotlin.reflect.full.primaryConstructor +import kotlin.reflect.jvm.javaType +import com.google.gson.internal.`$Gson$Types` as InternalGsonTypes + +@Retention(AnnotationRetention.RUNTIME) +@Target(AnnotationTarget.CLASS) +annotation class KSerializable( +) + + +@Retention(AnnotationRetention.RUNTIME) +@Target(AnnotationTarget.PROPERTY) +annotation class ExtraData + + +class KotlinTypeAdapterFactory : TypeAdapterFactory { + + internal data class ParameterInfo( + val param: KParameter, + val adapter: TypeAdapter<Any?>, + val name: String, + val field: KProperty1<Any, Any?> + ) + + override fun <T : Any> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? { + val kotlinClass = type.rawType.kotlin as KClass<T> + if (kotlinClass.findAnnotation<KSerializable>() == null) return null + if (!kotlinClass.isData) return null + val primaryConstructor = kotlinClass.primaryConstructor ?: return null + val params = primaryConstructor.parameters.filter { it.findAnnotation<ExtraData>() == null } + val extraDataParam = primaryConstructor.parameters + .find { it.findAnnotation<ExtraData>() != null } + ?.let { param -> + require(typeOf<MutableMap<String, JsonElement>>().isSubtypeOf(param.type)) + param to kotlinClass.memberProperties.find { it.name == param.name && it.returnType.isSubtypeOf(typeOf<Map<String, JsonElement>>()) } as KProperty1<Any, Map<String, JsonElement>> + } + val parameterInfos = params.map { param -> + ParameterInfo( + param, + gson.getAdapter( + TypeToken.get(InternalGsonTypes.resolve(type.type, type.rawType, param.type.javaType)) + ) as TypeAdapter<Any?>, + param.findAnnotation<SerializedName>()?.value ?: param.name!!, + kotlinClass.memberProperties.find { it.name == param.name }!! as KProperty1<Any, Any?> + ) + }.associateBy { it.name } + val jsonElementAdapter = gson.getAdapter(JsonElement::class.java) + + return object : TypeAdapter<T>() { + override fun write(out: JsonWriter, value: T?) { + if (value == null) { + out.nullValue() + return + } + out.beginObject() + parameterInfos.forEach { (name, paramInfo) -> + out.name(name) + paramInfo.adapter.write(out, paramInfo.field.get(value)) + } + if (extraDataParam != null) { + val extraData = extraDataParam.second.get(value) + extraData.forEach { (extraName, extraValue) -> + out.name(extraName) + jsonElementAdapter.write(out, extraValue) + } + } + out.endObject() + } + + override fun read(reader: JsonReader): T? { + if (reader.peek() == JsonToken.NULL) { + reader.nextNull() + return null + } + reader.beginObject() + val args = mutableMapOf<KParameter, Any?>() + val extraData = mutableMapOf<String, JsonElement>() + while (reader.peek() != JsonToken.END_OBJECT) { + val name = reader.nextName() + val paramData = parameterInfos[name] + if (paramData == null) { + extraData[name] = jsonElementAdapter.read(reader) + continue + } + val value = paramData.adapter.read(reader) + args[paramData.param] = value + } + reader.endObject() + if (extraDataParam == null) { + if (extraData.isNotEmpty()) { + throw JsonParseException("Encountered unknown keys ${extraData.keys} while parsing $type") + } + } else { + args[extraDataParam.first] = extraData + } + return primaryConstructor.callBy(args) + } + } + } +} + diff --git a/src/main/kotlin/util/identifierutil.kt b/src/main/kotlin/util/identifierutil.kt new file mode 100644 index 0000000..e94e15c --- /dev/null +++ b/src/main/kotlin/util/identifierutil.kt @@ -0,0 +1,11 @@ +package moe.nea.ultranotifier.util + +import moe.nea.ultranotifier.Constants +import net.minecraft.util.Identifier + +fun identifier(namespace: String, path: String): Identifier { + return Identifier(namespace, path) +} + +fun vanillaIdentifier(path: String) = identifier("minecraft", path) +fun ultraIdentifier(path: String) = identifier(Constants.MOD_ID, path) diff --git a/src/main/kotlin/util/iterutil.kt b/src/main/kotlin/util/iterutil.kt new file mode 100644 index 0000000..7845b05 --- /dev/null +++ b/src/main/kotlin/util/iterutil.kt @@ -0,0 +1,36 @@ +package moe.nea.ultranotifier.util + + +fun <T, K : Any> Sequence<T>.duplicatesBy(keyFunc: (T) -> K): Sequence<T> { + return object : Sequence<T> { + override fun iterator(): Iterator<T> { + val observed = HashSet<K>() + val oldIterator = this@duplicatesBy.iterator() + + return object : Iterator<T> { + var next: T? = null + var hasNext = false + override fun hasNext(): Boolean { + if (hasNext) return true + while (oldIterator.hasNext()) { + val elem = oldIterator.next() + val key = keyFunc(elem) + if (observed.add(key)) + continue + hasNext = true + next = elem + } + return hasNext + } + + override fun next(): T { + if (!hasNext()) throw NoSuchElementException() + hasNext = false + val elem = next as T + next = null + return elem + } + } + } + } +} diff --git a/src/main/kotlin/util/minecrat/MC.kt b/src/main/kotlin/util/minecrat/MC.kt new file mode 100644 index 0000000..d942982 --- /dev/null +++ b/src/main/kotlin/util/minecrat/MC.kt @@ -0,0 +1,11 @@ +package moe.nea.ultranotifier.util.minecrat + +import net.minecraft.client.MinecraftClient + +object MC { + val font get() = instance.textRenderer + val instance get() = MinecraftClient.getInstance() + val inGameHud get() = instance.inGameHud!! + val chatHud get() = inGameHud.chatHud!! + +} diff --git a/src/main/kotlin/util/minecrat/TextUtil.kt b/src/main/kotlin/util/minecrat/TextUtil.kt new file mode 100644 index 0000000..0640021 --- /dev/null +++ b/src/main/kotlin/util/minecrat/TextUtil.kt @@ -0,0 +1,70 @@ +package moe.nea.ultranotifier.util.minecrat +import net.minecraft.text.Text + +//#if MC > 1.16 +import net.minecraft.text.TextColor +import net.minecraft.util.Formatting +//#endif +//#if MC > 1.20 +import net.minecraft.text.MutableText +import net.minecraft.text.PlainTextContent +//#endif + +fun Text.getDirectlyContainedText() = +//#if MC < 1.16 +//$$ this.unformattedComponentText +//#elseif MC < 1.20 +//$$ this.asString() +//#else + (this.content as? PlainTextContent)?.string().orEmpty() +//#endif + +fun Text?.getFormattedTextCompat(): String = +//#if MC < 1.16 +//$$ this?.formattedText.orEmpty() +//#else +run { + this ?: return@run "" + val sb = StringBuilder() + for (component in iterator()) { + sb.append(component.style.color?.toChatFormatting()?.toString() ?: "§r") + sb.append(component.getDirectlyContainedText()) + sb.append("§r") + } + sb.toString() +} + +private val textColorLUT = Formatting.entries + .mapNotNull { formatting -> formatting.colorValue?.let { it to formatting } } + .toMap() + +fun TextColor.toChatFormatting(): Formatting? { + return textColorLUT[this.rgb] +} + +fun Text.iterator(): Sequence<Text> { + return sequenceOf(this) + siblings.asSequence().flatMap { it.iterator() } // TODO: in theory we want to properly inherit styles here +} +//#endif + +//#if MC > 1.20 +fun MutableText.withColor(formatting: Formatting): Text { + return this.styled { it.withColor(formatting) } +} +//#endif + +fun CharSequence.removeFormattingCodes(): String { + var nextParagraph = indexOf('§') + if (nextParagraph < 0) return this.toString() + val stringBuffer = StringBuilder(this.length) + var readIndex = 0 + while (nextParagraph >= 0) { + stringBuffer.append(this, readIndex, nextParagraph) + readIndex = nextParagraph + 2 + nextParagraph = indexOf('§', startIndex = readIndex) + if (readIndex > this.length) + readIndex = this.length + } + stringBuffer.append(this, readIndex, this.length) + return stringBuffer.toString() +} diff --git a/src/main/kotlin/util/minecrat/infer.kt b/src/main/kotlin/util/minecrat/infer.kt new file mode 100644 index 0000000..dc89392 --- /dev/null +++ b/src/main/kotlin/util/minecrat/infer.kt @@ -0,0 +1,22 @@ +@file:OptIn(ExperimentalContracts::class) + +package moe.nea.ultranotifier.util.minecrat + +import moe.nea.ultranotifier.datamodel.HasCategorizedChatLine +import moe.nea.ultranotifier.event.ChattyHudLine +import net.minecraft.client.gui.hud.ChatHud +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract + +interface AccessorChatHud { + val lineHeight_ultranotifier: Int +} + +fun ChatHud.accessor(): AccessorChatHud { + contract { + returns() implies (this@accessor is AccessorChatHud) + } + return this as AccessorChatHud +} + +val ChattyHudLine.category get() = (this as HasCategorizedChatLine).categorizedChatLine_ultraNotifier diff --git a/src/main/kotlin/util/render/ScreenRenderUtils.kt b/src/main/kotlin/util/render/ScreenRenderUtils.kt new file mode 100644 index 0000000..959d0a8 --- /dev/null +++ b/src/main/kotlin/util/render/ScreenRenderUtils.kt @@ -0,0 +1,135 @@ +package moe.nea.ultranotifier.util.render + +import gg.essential.universal.UGraphics +import gg.essential.universal.UMatrixStack +import juuxel.libninepatch.NinePatch +import juuxel.libninepatch.TextureRenderer +import moe.nea.ultranotifier.util.minecrat.MC +import net.minecraft.util.Identifier +import java.awt.Color +//#if MC > 1.16 +import net.minecraft.client.gui.DrawContext +import net.minecraft.client.util.math.MatrixStack + +//#endif + +object ScreenRenderUtils { + //#if MC > 1.16 + @JvmStatic + fun umatrix( + matrixStack: MatrixStack + ) = UMatrixStack(matrixStack) + + //#endif + //#if MC >= 1.20 + @JvmStatic + fun umatrix( + context: DrawContext + ) = UMatrixStack(context.matrices) + //#endif + + @JvmStatic + fun umatrix() = UMatrixStack() + + fun fillRect( + matrixStack: UMatrixStack, + left: Double, top: Double, + right: Double, bottom: Double, + color: Color, + ) { + val buffer = UGraphics.getFromTessellator() + buffer.beginWithDefaultShader(UGraphics.DrawMode.QUADS, UGraphics.CommonVertexFormats.POSITION_COLOR) + buffer.pos(matrixStack, left, top, 0.0).color(color).endVertex() + buffer.pos(matrixStack, left, bottom, 0.0).color(color).endVertex() + buffer.pos(matrixStack, right, bottom, 0.0).color(color).endVertex() + buffer.pos(matrixStack, right, top, 0.0).color(color).endVertex() + buffer.drawDirect() + } + + fun renderTexture( + identifier: Identifier, + matrixStack: UMatrixStack, + left: Double, top: Double, + right: Double, bottom: Double, + ) { + UGraphics.bindTexture(0, identifier) + val graphics = UGraphics.getFromTessellator() + graphics.beginWithDefaultShader(UGraphics.DrawMode.QUADS, UGraphics.CommonVertexFormats.POSITION_TEXTURE) + graphics.pos(matrixStack, left, top, 0.0).tex(0.0, 0.0).endVertex() + graphics.pos(matrixStack, left, bottom, 0.0).tex(0.0, 1.0).endVertex() + graphics.pos(matrixStack, right, bottom, 0.0).tex(1.0, 1.0).endVertex() + graphics.pos(matrixStack, right, top, 0.0).tex(1.0, 0.0).endVertex() + graphics.drawDirect() + } + + fun renderNineSlice( + ninePatch: NinePatch<Identifier>, + matrixStack: UMatrixStack, + left: Double, top: Double, + right: Double, bottom: Double, + ) { + class Saver : TextureRenderer<Identifier> { + override fun draw( + texture: Identifier?, + x: Int, + y: Int, + width: Int, + height: Int, + u1: Float, + v1: Float, + u2: Float, + v2: Float + ) { + this.texture = texture + } + + var texture: Identifier? = null + } + + val saver = Saver() + ninePatch.draw(saver, 1, 1) + UGraphics.bindTexture(0, saver.texture!!) + val graphics = UGraphics.getFromTessellator() + graphics.beginWithDefaultShader(UGraphics.DrawMode.QUADS, UGraphics.CommonVertexFormats.POSITION_TEXTURE) + ninePatch.draw(object : TextureRenderer<Identifier> { + override fun draw( + texture: Identifier, + x: Int, + y: Int, + width: Int, + height: Int, + u1: Float, + v1: Float, + u2: Float, + v2: Float + ) { + val x1 = left + x.toDouble() + val y1 = top + y.toDouble() + val x2 = x1 + width + val y2 = y1 + height + graphics.pos(matrixStack, x1, y1, 0.0) + .tex(u1.toDouble(), v1.toDouble()) + .endVertex() + graphics.pos(matrixStack, x1, y2, 0.0) + .tex(u1.toDouble(), v2.toDouble()) + .endVertex() + graphics.pos(matrixStack, x2, y2, 0.0) + .tex(u2.toDouble(), v2.toDouble()) + .endVertex() + graphics.pos(matrixStack, x2, y1, 0.0) + .tex(u2.toDouble(), v1.toDouble()) + .endVertex() + } + }, (right - left).toInt(), (bottom - top).toInt()) + graphics.drawDirect() + } + + fun getTextWidth(text: String): Int { + return MC.font.getWidth(text) + } + + fun renderText(matrixStack: UMatrixStack, x: Double, y: Double, text: String) { + UGraphics.drawString(matrixStack, text, x.toFloat(), y.toFloat(), -1, false) + } + +} diff --git a/src/main/resources/assets/ultranotifier/textures/gui/square_panel.png b/src/main/resources/assets/ultranotifier/textures/gui/square_panel.png Binary files differnew file mode 100644 index 0000000..fdca83b --- /dev/null +++ b/src/main/resources/assets/ultranotifier/textures/gui/square_panel.png diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index df30b70..3d7d0bd 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -4,6 +4,9 @@ "name": "${modName}", "description": "${description}", "version": "${version}", + "mixins": [ + "mixins.ultranotifier.json" + ], "entrypoints": { "main": ["moe.nea.ultranotifier.UltraNotifierEntryPoint"] }, diff --git a/versions/mapping-1.14.4-1.14.4-forge.txt b/versions/mapping-1.12.2-1.8.9.txt index e69de29..e69de29 100644 --- a/versions/mapping-1.14.4-1.14.4-forge.txt +++ b/versions/mapping-1.12.2-1.8.9.txt diff --git a/versions/mapping-1.14.4-forge-1.8.9.txt b/versions/mapping-1.14.4-forge-1.8.9.txt deleted file mode 100644 index cdd8783..0000000 --- a/versions/mapping-1.14.4-forge-1.8.9.txt +++ /dev/null @@ -1,5 +0,0 @@ -net.minecraft.client.gui.NewChatGui net.minecraft.client.gui.GuiNewChat -net.minecraft.util.text.ITextComponent net.minecraft.util.IChatComponent -net.minecraft.util.text.ITextComponent getString() getUnformattedText() -net.minecraftforge.eventbus.api.Event net.minecraftforge.fml.common.eventhandler.Event -net.minecraftforge.eventbus.api.SubscribeEvent net.minecraftforge.fml.common.eventhandler.SubscribeEvent diff --git a/versions/mapping-1.20.6-1.14.4.txt b/versions/mapping-1.16.2-fabric-1.16.2-forge.txt index e69de29..e69de29 100644 --- a/versions/mapping-1.20.6-1.14.4.txt +++ b/versions/mapping-1.16.2-fabric-1.16.2-forge.txt diff --git a/versions/mapping-1.16.2-forge-1.12.2.txt b/versions/mapping-1.16.2-forge-1.12.2.txt new file mode 100644 index 0000000..08243e4 --- /dev/null +++ b/versions/mapping-1.16.2-forge-1.12.2.txt @@ -0,0 +1,16 @@ +net.minecraft.network.chat.Component net.minecraft.util.text.ITextComponent +net.minecraft.network.chat.Component getString() getUnformattedText() +net.minecraftforge.eventbus.api.Event net.minecraftforge.fml.common.eventhandler.Event +net.minecraftforge.eventbus.api.SubscribeEvent net.minecraftforge.fml.common.eventhandler.SubscribeEvent +net.minecraft.client.gui.screens.Screen net.minecraft.client.gui.GuiScreen +net.minecraft.client.gui.components.ChatComponent render() drawChat() +net.minecraftforge.event.TickEvent net.minecraftforge.fml.common.gameevent.TickEvent +net.minecraftforge.event.TickEvent$ClientTickEvent net.minecraftforge.fml.common.gameevent.TickEvent$ClientTickEvent +net.minecraft.client.Options textBackgroundOpacity chatOpacity +net.minecraft.client.gui.components.ChatComponent getScale() getChatScale() +net.minecraft.client.gui.components.ChatComponent getLinesPerPage() getLineCount() +net.minecraft.client.gui.components.ChatComponent addMessage(Lnet/minecraft/network/chat/Component;IIZ)V setChatLine() +net.minecraft.client.gui.components.ChatComponent net.minecraft.client.gui.GuiNewChat +net.minecraft.client.gui.components.ComponentRenderUtils net.minecraft.client.gui.GuiUtilRenderComponents +net.minecraft.client.GuiMessage net.minecraft.client.gui.ChatLine +net.minecraft.client.GuiMessage getMessage() getChatComponent() |