diff --git a/api/api/api.api b/api/api/api.api index a1286ab..609ad19 100644 --- a/api/api/api.api +++ b/api/api/api.api @@ -423,13 +423,13 @@ public abstract interface class gg/essential/api/utils/GuiUtil { public abstract fun openScreen (Lnet/minecraft/client/gui/screens/Screen;)V @1.17.1-forge,1.18.2-forge,1.19.2-forge,1.19.3-forge,1.19.4-forge,1.20.1-forge,1.20.2-forge,1.20.4-forge public abstract fun openedScreen ()Lnet/minecraft/client/gui/screens/Screen; - @1.16.2-fabric,1.16.2-forge,1.17.1-fabric,1.18.1-fabric,1.18.2-fabric,1.19-fabric,1.19.2-fabric,1.19.3-fabric,1.19.4-fabric,1.20-fabric,1.20.1-fabric,1.20.2-fabric,1.20.4-fabric,1.20.6-fabric,1.21-fabric + @1.16.2-fabric,1.16.2-forge,1.17.1-fabric,1.18.1-fabric,1.18.2-fabric,1.19-fabric,1.19.2-fabric,1.19.3-fabric,1.19.4-fabric,1.20-fabric,1.20.1-fabric,1.20.2-fabric,1.20.4-fabric,1.20.6-fabric,1.21-fabric,1.21.2-fabric public static fun getOpenedScreen ()Lnet/minecraft/client/gui/screen/Screen; - @1.16.2-fabric,1.16.2-forge,1.17.1-fabric,1.18.1-fabric,1.18.2-fabric,1.19-fabric,1.19.2-fabric,1.19.3-fabric,1.19.4-fabric,1.20-fabric,1.20.1-fabric,1.20.2-fabric,1.20.4-fabric,1.20.6-fabric,1.21-fabric + @1.16.2-fabric,1.16.2-forge,1.17.1-fabric,1.18.1-fabric,1.18.2-fabric,1.19-fabric,1.19.2-fabric,1.19.3-fabric,1.19.4-fabric,1.20-fabric,1.20.1-fabric,1.20.2-fabric,1.20.4-fabric,1.20.6-fabric,1.21-fabric,1.21.2-fabric public static fun open (Lnet/minecraft/client/gui/screen/Screen;)V - @1.16.2-fabric,1.16.2-forge,1.17.1-fabric,1.18.1-fabric,1.18.2-fabric,1.19-fabric,1.19.2-fabric,1.19.3-fabric,1.19.4-fabric,1.20-fabric,1.20.1-fabric,1.20.2-fabric,1.20.4-fabric,1.20.6-fabric,1.21-fabric + @1.16.2-fabric,1.16.2-forge,1.17.1-fabric,1.18.1-fabric,1.18.2-fabric,1.19-fabric,1.19.2-fabric,1.19.3-fabric,1.19.4-fabric,1.20-fabric,1.20.1-fabric,1.20.2-fabric,1.20.4-fabric,1.20.6-fabric,1.21-fabric,1.21.2-fabric public abstract fun openScreen (Lnet/minecraft/client/gui/screen/Screen;)V - @1.16.2-fabric,1.16.2-forge,1.17.1-fabric,1.18.1-fabric,1.18.2-fabric,1.19-fabric,1.19.2-fabric,1.19.3-fabric,1.19.4-fabric,1.20-fabric,1.20.1-fabric,1.20.2-fabric,1.20.4-fabric,1.20.6-fabric,1.21-fabric + @1.16.2-fabric,1.16.2-forge,1.17.1-fabric,1.18.1-fabric,1.18.2-fabric,1.19-fabric,1.19.2-fabric,1.19.3-fabric,1.19.4-fabric,1.20-fabric,1.20.1-fabric,1.20.2-fabric,1.20.4-fabric,1.20.6-fabric,1.21-fabric,1.21.2-fabric public abstract fun openedScreen ()Lnet/minecraft/client/gui/screen/Screen; @1.12.2-forge,1.8.9-forge public static fun getOpenedScreen ()Lnet/minecraft/client/gui/GuiScreen; @@ -446,9 +446,9 @@ public final class gg/essential/api/utils/GuiUtil$Companion { public final fun getOpenedScreen ()Lnet/minecraft/client/gui/screens/Screen; @1.17.1-forge,1.18.2-forge,1.19.2-forge,1.19.3-forge,1.19.4-forge,1.20.1-forge,1.20.2-forge,1.20.4-forge public final fun open (Lnet/minecraft/client/gui/screens/Screen;)V - @1.16.2-fabric,1.16.2-forge,1.17.1-fabric,1.18.1-fabric,1.18.2-fabric,1.19-fabric,1.19.2-fabric,1.19.3-fabric,1.19.4-fabric,1.20-fabric,1.20.1-fabric,1.20.2-fabric,1.20.4-fabric,1.20.6-fabric,1.21-fabric + @1.16.2-fabric,1.16.2-forge,1.17.1-fabric,1.18.1-fabric,1.18.2-fabric,1.19-fabric,1.19.2-fabric,1.19.3-fabric,1.19.4-fabric,1.20-fabric,1.20.1-fabric,1.20.2-fabric,1.20.4-fabric,1.20.6-fabric,1.21-fabric,1.21.2-fabric public final fun getOpenedScreen ()Lnet/minecraft/client/gui/screen/Screen; - @1.16.2-fabric,1.16.2-forge,1.17.1-fabric,1.18.1-fabric,1.18.2-fabric,1.19-fabric,1.19.2-fabric,1.19.3-fabric,1.19.4-fabric,1.20-fabric,1.20.1-fabric,1.20.2-fabric,1.20.4-fabric,1.20.6-fabric,1.21-fabric + @1.16.2-fabric,1.16.2-forge,1.17.1-fabric,1.18.1-fabric,1.18.2-fabric,1.19-fabric,1.19.2-fabric,1.19.3-fabric,1.19.4-fabric,1.20-fabric,1.20.1-fabric,1.20.2-fabric,1.20.4-fabric,1.20.6-fabric,1.21-fabric,1.21.2-fabric public final fun open (Lnet/minecraft/client/gui/screen/Screen;)V @1.12.2-forge,1.8.9-forge public final fun getOpenedScreen ()Lnet/minecraft/client/gui/GuiScreen; @@ -512,7 +512,7 @@ public final class gg/essential/api/utils/KotlinAdapter : net/minecraftforge/fml public abstract interface class gg/essential/api/utils/MinecraftUtils { @1.17.1-forge,1.18.2-forge,1.19.2-forge,1.19.3-forge,1.19.4-forge,1.20.1-forge,1.20.2-forge,1.20.4-forge public abstract fun getResourceImage (Lnet/minecraft/resources/ResourceLocation;)Ljava/awt/image/BufferedImage; - @1.16.2-fabric,1.17.1-fabric,1.18.1-fabric,1.18.2-fabric,1.19-fabric,1.19.2-fabric,1.19.3-fabric,1.19.4-fabric,1.20-fabric,1.20.1-fabric,1.20.2-fabric,1.20.4-fabric,1.20.6-fabric,1.21-fabric + @1.16.2-fabric,1.17.1-fabric,1.18.1-fabric,1.18.2-fabric,1.19-fabric,1.19.2-fabric,1.19.3-fabric,1.19.4-fabric,1.20-fabric,1.20.1-fabric,1.20.2-fabric,1.20.4-fabric,1.20.6-fabric,1.21-fabric,1.21.2-fabric public abstract fun getResourceImage (Lnet/minecraft/util/Identifier;)Ljava/awt/image/BufferedImage; @1.12.2-forge,1.16.2-forge,1.8.9-forge public abstract fun getResourceImage (Lnet/minecraft/util/ResourceLocation;)Ljava/awt/image/BufferedImage; diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index b63507a..923649c 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -36,7 +36,7 @@ dependencies { implementation("org.ow2.asm:asm-commons:9.3") implementation ("com.google.guava:guava:30.1.1-jre") - implementation("gg.essential:essential-gradle-toolkit:0.6.2") + implementation("gg.essential:essential-gradle-toolkit:0.6.3") } gradlePlugin { diff --git a/build-logic/src/main/kotlin/essential/preprocessor.kt b/build-logic/src/main/kotlin/essential/preprocessor.kt index 3891085..644db54 100644 --- a/build-logic/src/main/kotlin/essential/preprocessor.kt +++ b/build-logic/src/main/kotlin/essential/preprocessor.kt @@ -20,6 +20,7 @@ fun Project.configurePreprocessTree(versions: File) { configure { strictExtraMappings.set(true) + val fabric12102 = createNode("1.21.2-fabric", 12102, "yarn") val fabric12100 = createNode("1.21-fabric", 12100, "yarn") val fabric12006 = createNode("1.20.6-fabric", 12006, "yarn") val forge12004 = createNode("1.20.4-forge", 12004, "srg") @@ -46,6 +47,7 @@ fun Project.configurePreprocessTree(versions: File) { val forge11202 = createNode("1.12.2-forge", 11202, "srg") val forge10809 = createNode("1.8.9-forge", 10809, "srg") + fabric12102.link(fabric12100) fabric12100.link(fabric12006) fabric12006.link(fabric12004) forge12004.link(fabric12004) diff --git a/build.gradle.kts b/build.gradle.kts index cd2a29a..890fea9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -43,6 +43,7 @@ dependencies { implementation(bundle(project(":feature-flags"))!!) implementation(bundle(project(":libs"))!!) implementation(bundle(project(":infra"))!!) + implementation(bundle(project(":vigilance2"))!!) implementation(bundle(project(":gui:elementa"))!!) implementation(bundle(project(":gui:essential"))!!) implementation(bundle(project(":gui:vigilance"))!!) @@ -59,6 +60,7 @@ dependencies { implementation(bundle(project(":lwjgl3"))!!) runtimeOnly(bundle(project(":lwjgl3:impl"))!!) + // In order to get proper IDE support, we want to use a non-relocated MixinExtras version in dev. // This gets transformed by `relocatedJar` to use our bundled relocated version for production. implementation(annotationProcessor("io.github.llamalad7:mixinextras-common:${libs.versions.mixinextras.get()}")!!) @@ -124,6 +126,7 @@ dependencies { val fapiVersion = when (platform.mcVersion) { 12006 -> "0.97.8+1.20.6" 12100 -> "0.99.2+1.21" + 12102 -> "0.106.0+1.21.2" else -> error("No fabric API version configured!") } include(modImplementation(fabricApi.module("fabric-api-base", fapiVersion))!!) @@ -166,7 +169,7 @@ tasks.jar { if (!platform.isFabric) { manifest { if (mcVersion >= 11400) { - attributes("MixinConfigs" to "mixins.essential.json,mixins.essential.init.json,mixins.essential.modcompat.json") + attributes("MixinConfigs" to "mixins.essential.json,mixins.essential.init.json,mixins.essential.modcompat.json,mixins.essential.tests.json") attributes("Requires-Essential-Stage2-Version" to "1.6.0") } } diff --git a/changelog/release-1.3.4.2.md b/changelog/release-1.3.4.2.md new file mode 100644 index 0000000..1e7ad8a --- /dev/null +++ b/changelog/release-1.3.4.2.md @@ -0,0 +1,19 @@ +Title: Bug Patch +Summary: Minor bug fixes + +## New Versions +- Added support for 1.21.2 Fabric + +## Wardrobe +- Added slow rotation to some bundle items so they can be more easily seen from all sides + +## Bug Fixes +- Fixed servers in the featured tab displaying as infinitely pinging on 1.20.6+ +- Fixed replacing selected text sometimes inserting the new text in the wrong position, potentially crashing the game on Ctrl+Z +- Fixed outer skin layer sometimes being visible over cosmetics when it shouldn't be +- Fixed the re-invite button remaining visible for players already online +- Fixed emote getting stuck as active when switching outfit while emote is active +- Fixed the Screenshot Browser freezing under certain circumstances + +## Compatibility +- Fixed emotes not playing properly with the Fresh Moves resource pack installed diff --git a/gui/elementa/src/main/kotlin/gg/essential/gui/elementa/effects/AlphaEffect.kt b/elementa/layoutdsl/src/main/kotlin/gg/essential/gui/effects/AlphaEffect.kt similarity index 96% rename from gui/elementa/src/main/kotlin/gg/essential/gui/elementa/effects/AlphaEffect.kt rename to elementa/layoutdsl/src/main/kotlin/gg/essential/gui/effects/AlphaEffect.kt index 606f94e..e91a2ca 100644 --- a/gui/elementa/src/main/kotlin/gg/essential/gui/elementa/effects/AlphaEffect.kt +++ b/elementa/layoutdsl/src/main/kotlin/gg/essential/gui/effects/AlphaEffect.kt @@ -9,7 +9,7 @@ * commercialize, or otherwise exploit, or create derivative works based * upon, this file or any other in this repository, all of which is reserved by Essential. */ -package gg.essential.gui.elementa.effects +package gg.essential.gui.effects import gg.essential.elementa.effects.Effect import gg.essential.elementa.state.State @@ -19,7 +19,6 @@ import gg.essential.universal.UResolution import gg.essential.universal.shader.BlendState import gg.essential.universal.shader.SamplerUniform import gg.essential.universal.shader.UShader -import gg.essential.util.GuiElementaPlatform.Companion.platform import org.lwjgl.opengl.GL11 import java.io.Closeable import java.lang.ref.PhantomReference @@ -99,10 +98,10 @@ class AlphaEffect(private val alphaState: State) : Effect() { var prevAlphaTestFunc = 0 var prevAlphaTestRef = 0f - if (!platform.isCoreProfile) { + if (!UGraphics.isCoreProfile()) { prevAlphaTestFunc = GL11.glGetInteger(GL11.GL_ALPHA_TEST_FUNC) prevAlphaTestRef = GL11.glGetFloat(GL11.GL_ALPHA_TEST_REF) - platform.glAlphaFunc(GL11.GL_ALWAYS, 0f) + UGraphics.alphaFunc(GL11.GL_ALWAYS, 0f) } shader.bind() @@ -118,8 +117,8 @@ class AlphaEffect(private val alphaState: State) : Effect() { shader.unbind() - if (!platform.isCoreProfile) { - platform.glAlphaFunc(prevAlphaTestFunc, prevAlphaTestRef) + if (!UGraphics.isCoreProfile()) { + UGraphics.alphaFunc(prevAlphaTestFunc, prevAlphaTestRef) } } diff --git a/gui/elementa/src/main/kotlin/gg/essential/gui/elementa/effects/GradientEffect.kt b/elementa/layoutdsl/src/main/kotlin/gg/essential/gui/effects/GradientEffect.kt similarity index 93% rename from gui/elementa/src/main/kotlin/gg/essential/gui/elementa/effects/GradientEffect.kt rename to elementa/layoutdsl/src/main/kotlin/gg/essential/gui/effects/GradientEffect.kt index b471d82..4f8a17e 100644 --- a/gui/elementa/src/main/kotlin/gg/essential/gui/elementa/effects/GradientEffect.kt +++ b/elementa/layoutdsl/src/main/kotlin/gg/essential/gui/effects/GradientEffect.kt @@ -9,7 +9,7 @@ * commercialize, or otherwise exploit, or create derivative works based * upon, this file or any other in this repository, all of which is reserved by Essential. */ -package gg.essential.gui.elementa.effects +package gg.essential.gui.effects import gg.essential.elementa.effects.Effect import gg.essential.gui.elementa.state.v2.State @@ -17,7 +17,6 @@ import gg.essential.universal.UGraphics import gg.essential.universal.UMatrixStack import gg.essential.universal.shader.BlendState import gg.essential.universal.shader.UShader -import gg.essential.util.GuiElementaPlatform.Companion.platform import org.intellij.lang.annotations.Language import org.lwjgl.opengl.GL11 import java.awt.Color @@ -66,10 +65,10 @@ class GradientEffect( var prevAlphaTestFunc = 0 var prevAlphaTestRef = 0f - if (!platform.isCoreProfile) { + if (!UGraphics.isCoreProfile()) { prevAlphaTestFunc = GL11.glGetInteger(GL11.GL_ALPHA_TEST_FUNC) prevAlphaTestRef = GL11.glGetFloat(GL11.GL_ALPHA_TEST_REF) - platform.glAlphaFunc(GL11.GL_ALWAYS, 0f) + UGraphics.alphaFunc(GL11.GL_ALWAYS, 0f) } // See UIBlock.drawBlock for why we use this depth function @@ -79,8 +78,8 @@ class GradientEffect( UGraphics.disableDepth() UGraphics.depthFunc(GL11.GL_LEQUAL) - if (!platform.isCoreProfile) { - platform.glAlphaFunc(prevAlphaTestFunc, prevAlphaTestRef) + if (!UGraphics.isCoreProfile()) { + UGraphics.alphaFunc(prevAlphaTestFunc, prevAlphaTestRef) } if (dither) { diff --git a/gui/elementa/src/main/kotlin/gg/essential/gui/layoutdsl/gradient.kt b/elementa/layoutdsl/src/main/kotlin/gg/essential/gui/layoutdsl/gradient.kt similarity index 96% rename from gui/elementa/src/main/kotlin/gg/essential/gui/layoutdsl/gradient.kt rename to elementa/layoutdsl/src/main/kotlin/gg/essential/gui/layoutdsl/gradient.kt index a76ed05..dced6d5 100644 --- a/gui/elementa/src/main/kotlin/gg/essential/gui/layoutdsl/gradient.kt +++ b/elementa/layoutdsl/src/main/kotlin/gg/essential/gui/layoutdsl/gradient.kt @@ -12,7 +12,7 @@ package gg.essential.gui.layoutdsl import gg.essential.elementa.effects.Effect -import gg.essential.gui.elementa.effects.GradientEffect +import gg.essential.gui.effects.GradientEffect import gg.essential.gui.elementa.state.v2.State import gg.essential.gui.elementa.state.v2.stateOf import java.awt.Color diff --git a/gui/elementa/src/main/kotlin/gg/essential/gui/elementa/transitions/FadeInTransition.kt b/elementa/layoutdsl/src/main/kotlin/gg/essential/gui/transitions/FadeInTransition.kt similarity index 94% rename from gui/elementa/src/main/kotlin/gg/essential/gui/elementa/transitions/FadeInTransition.kt rename to elementa/layoutdsl/src/main/kotlin/gg/essential/gui/transitions/FadeInTransition.kt index ed254f2..92be0f0 100644 --- a/gui/elementa/src/main/kotlin/gg/essential/gui/elementa/transitions/FadeInTransition.kt +++ b/elementa/layoutdsl/src/main/kotlin/gg/essential/gui/transitions/FadeInTransition.kt @@ -9,13 +9,13 @@ * commercialize, or otherwise exploit, or create derivative works based * upon, this file or any other in this repository, all of which is reserved by Essential. */ -package gg.essential.gui.elementa.transitions +package gg.essential.gui.transitions import gg.essential.elementa.constraints.animation.AnimatingConstraints import gg.essential.elementa.constraints.animation.Animations import gg.essential.elementa.state.BasicState import gg.essential.elementa.transitions.BoundTransition -import gg.essential.gui.elementa.effects.AlphaEffect +import gg.essential.gui.effects.AlphaEffect import kotlin.properties.Delegates /** diff --git a/elementa/layoutdsl/src/main/kotlin/gg/essential/gui/transitions/FadeOutTransition.kt b/elementa/layoutdsl/src/main/kotlin/gg/essential/gui/transitions/FadeOutTransition.kt new file mode 100644 index 0000000..c316acf --- /dev/null +++ b/elementa/layoutdsl/src/main/kotlin/gg/essential/gui/transitions/FadeOutTransition.kt @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.gui.transitions + +import gg.essential.elementa.constraints.animation.AnimatingConstraints +import gg.essential.elementa.constraints.animation.Animations +import gg.essential.elementa.state.BasicState +import gg.essential.elementa.transitions.BoundTransition +import gg.essential.gui.effects.AlphaEffect +import kotlin.properties.Delegates + +/** + * Fades a component and all of its children out. This is done using + * [AlphaEffect]. When the transition is finished, the effect is removed. + * Typically, one would hide the component after this transition is finished. + */ +class FadeOutTransition @JvmOverloads constructor( + private val time: Float = 1f, + private val animationType: Animations = Animations.OUT_EXP, +) : BoundTransition() { + + private val alphaState = BasicState(1f) + private var alpha by Delegates.observable(1f) { _, _, newValue -> + alphaState.set(newValue) + } + + private val effect = AlphaEffect(alphaState) + + override fun beforeTransition() { + boundComponent.enableEffect(effect) + } + + override fun doTransition(constraints: AnimatingConstraints) { + constraints.setExtraDelay(time) + boundComponent.apply { + ::alpha.animate(animationType, time, 0f) + } + } + + override fun afterTransition() { + boundComponent.removeEffect(effect) + effect.cleanup() + } +} \ No newline at end of file diff --git a/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/compatibility.kt b/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/compatibility.kt index 8e6b825..434f80a 100644 --- a/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/compatibility.kt +++ b/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/compatibility.kt @@ -16,12 +16,8 @@ import java.util.function.Consumer import gg.essential.elementa.state.State as V1State private class V2AsV1State(private val v2State: State, owner: ReferenceHolder) : V1State() { - // Stored in a field, so the listener is kept alive at least as long as this legacy state instance exists - private val listener: (T) -> Unit = { super.set(it) } - - init { - v2State.onSetValue(owner, listener) - } + @Suppress("unused") // keep effect alive at least as long as this legacy state instance exists + private val effect = effect(owner) { super.set(v2State()) } override fun get(): T = v2State.get() @@ -43,7 +39,7 @@ private class V2AsV1State(private val v2State: State, owner: ReferenceHold * * Note that as with any listener on a v2 state, the returned v1 state may be garbage collected once there are no more * strong references to it. This v2 state will not by itself keep it alive. - * The [owner] argument serves to prevent this from happening too early, see [State.onSetValue]. + * The [owner] argument serves to prevent this from happening too early, see [effect]. */ fun State.toV1(owner: ReferenceHolder): V1State = V2AsV1State(this, owner) @@ -72,7 +68,10 @@ fun V1State.toV2(): MutableState { } }) - return v2 + return object : MutableState by v2 { + @Suppress("unused") // keep this alive for as long as the returned v2 state + val referenceHolder = referenceHolder + } } /** diff --git a/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/coroutine.kt b/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/coroutine.kt index 52ba6a7..77daa14 100644 --- a/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/coroutine.kt +++ b/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/coroutine.kt @@ -18,6 +18,9 @@ import kotlin.coroutines.resume /** Waits until this [State] has a value which [equals] the given [value]. */ suspend fun State.awaitValue(value: T): T = await { it == value } +/** Waits until this [State] has a non-null value. */ +suspend fun State.awaitNotNull(): T = await { it != null }!! + /** Waits until this [State] has a value for which [accept] returns `true` and returns that value. */ suspend fun State.await(accept: (T) -> Boolean): T { // Fast-path @@ -25,24 +28,82 @@ suspend fun State.await(accept: (T) -> Boolean): T { // Slow path return suspendCancellableCoroutine { continuation -> - lateinit var unregister: () -> Unit - var listener: ((T) -> Unit)? - listener = { value -> + // We need to have the continuation carry a reference to the unregister function (we do this via + // invokeOnCancellation at the end of this block), so the effect is kept alive at least as long as the coroutine + // has any interest in it. + // The same reference also serves as a cancellation indicator: If it's been set to `null`, the continuation + // was cancelled and we can unregister the effect the next time it's called. + var referenceFromContinuation: Any? = null + + var unregister: (() -> Unit)? = null + unregister = onChange(ReferenceHolder.Weak) { value -> + if (referenceFromContinuation == null) { + unregister?.invoke() + unregister = null + return@onChange + } if (accept(value)) { - unregister() + unregister?.invoke() + unregister = null continuation.resume(value) } } - unregister = onSetValue(ReferenceHolder.Weak, listener) - listener(get()) + + referenceFromContinuation = unregister continuation.invokeOnCancellation { // Note: we cannot call `unregister` here because `invokeOnCancellation` makes no guarantee about which // thread we run on, and `unregister` isn't thread safe. - // So we'll instead merely drop our reference to the listener and leave it to State's weakness properties - // to clean up the registration. - // This does mean our callback will continue to be invoked, but `CancellableCoroutine` is fine with that - // because cancellation may race with `resume` in pretty much any code. - listener = null + // So we'll instead merely drop our reference to the unregister function and leave it to State's weakness + // properties (or the next onChange invocation) to clean up the registration. + referenceFromContinuation = null + } + } +} + +fun State.currentAndFutureValues(): StateIterable = object : StateIterable { + override fun iterator(): StateIterator { + return object : StateIterator { + private var initial = true + private var next: T = getUntracked() + + override suspend fun hasNext(): Boolean { + if (initial) { + return true + } + next = await { it != next } + return true + } + + override fun next(): T { + initial = false + return next + } } } } + +fun State.futureValues(excludingInitial: T = getUntracked()): StateIterable = object : StateIterable { + override fun iterator(): StateIterator { + return object : StateIterator { + private var next: T = excludingInitial + + override suspend fun hasNext(): Boolean { + next = await { it != next } + return true + } + + override fun next(): T { + return next + } + } + } +} + +interface StateIterable { + operator fun iterator(): StateIterator +} + +interface StateIterator { + suspend operator fun hasNext(): Boolean + operator fun next(): T +} diff --git a/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/state.kt b/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/state.kt index 74d633b..07f2288 100644 --- a/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/state.kt +++ b/elementa/statev2/src/main/kotlin/gg/essential/gui/elementa/state/v2/state.kt @@ -194,30 +194,6 @@ fun interface State { @Deprecated("Calls to this method are not tracked. If this is intentional, use `getUntracked` instead.") fun get(): T = getUntracked() - /** - * Register a listener which will be called whenever the value of this State object changes - * - * The listener registration is weak by default. This means that no strong reference to the - * listener is kept in this State object and your listener may be garbage collected if no other - * strong references to it exist. Once a listener is garbage collected, it will (obviously) no - * longer receive updates. - * - * Keeping a strong reference to your own listener is easy to forget, so this method requires you - * to explicitly pass in an object which will maintain a strong reference to your listener for - * you. With that, your listener will stay active **at least** as long as the given [owner] is - * alive (unless the returned callback in invoked). - * - * In general, the lifetime of your listener should match the lifetime of the passed [owner], - * usually the thing (e.g. [UIComponent]) the listener is modifying. If the owner far outlives - * your listener, you may be leaking memory because the owner will keep all those listeners and - * anything they reference alive far beyond the point where they are needed. If your listener - * outlives the owner, then it may become inactive sooner than you expected and whatever it is - * updating might no longer update properly. - * - * If you wish to manually keep your listener alive, pass [ReferenceHolder.Weak] as the owner. - * - * @return A callback which, when invoked, removes this listener - */ @Deprecated("If this method is used to update dependent states, use `stateBy` instead.\n" + "Otherwise the State system cannot be guaranteed that downsteam states have a consistent view of upstream" + "values (i.e. so called \"glitches\" may occur) and all dependences will be forced to evaluate eagerly" + @@ -305,6 +281,16 @@ fun derivedState( builder: (owner: ReferenceHolder, derivedState: MutableState) -> Unit, ): State = impl.derivedState(initialValue, builder) +/** + * Combines this [State] with the given custom [setter] function to create a custom [MutableState]. + */ +fun > S.withSetter(setter: S.(update: (value: T) -> T) -> Unit): MutableState = + object : MutableState, State by this { + override fun set(mapper: (T) -> T) { + setter(mapper) + } + } + /** A simple, immutable implementation of [State] */ private class ImmutableState(private val value: T) : State { override fun get(): T = value diff --git a/gradle.properties b/gradle.properties index e21c833..0d7a184 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ essential.defaults.loom=0 -essential.defaults.loom.fabric-loader=net.fabricmc:fabric-loader:0.15.11 +essential.defaults.loom.fabric-loader=net.fabricmc:fabric-loader:0.16.7 kotlin.stdlib.default.dependency=false org.gradle.daemon=false org.gradle.parallel=true @@ -7,4 +7,4 @@ org.gradle.configureondemand=true org.gradle.parallel.threads=128 org.gradle.jvmargs=-Xmx16G minecraftVersion=11202 -version=1.3.4.1 +version=1.3.4.2 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index baf8a20..52185a2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] -universalcraft = "362" -elementa = "665" +universalcraft = "365" +elementa = "670" vigilance = "306" mixinextras = "0.3.5" diff --git a/gui/essential/build.gradle.kts b/gui/essential/build.gradle.kts index a0c9481..cfce6b4 100644 --- a/gui/essential/build.gradle.kts +++ b/gui/essential/build.gradle.kts @@ -14,6 +14,7 @@ import gg.essential.gradle.util.KotlinVersion plugins { kotlin("jvm") + id("org.jetbrains.kotlin.plugin.serialization") id("gg.essential.defaults") } @@ -26,10 +27,14 @@ dependencies { implementation(project(":libs")) implementation(project(":infra")) implementation(project(":cosmetics", configuration = "minecraftRuntimeElements")) + implementation(project(":vigilance2")) implementation(project(":gui:elementa")) implementation(project(":ice")) implementation(project(":quic-connector")) + // For NotificationBuilder + compileOnly(project(":api:1.12.2-forge")) { isTransitive = false} + implementation("org.jitsi:ice4j:3.0-52-ga9ba80e") { isTransitive = false // for PseudoTCPBase only } diff --git a/src/main/java/gg/essential/network/connectionmanager/queue/PacketQueue.java b/gui/essential/src/main/java/gg/essential/network/connectionmanager/queue/PacketQueue.java similarity index 100% rename from src/main/java/gg/essential/network/connectionmanager/queue/PacketQueue.java rename to gui/essential/src/main/java/gg/essential/network/connectionmanager/queue/PacketQueue.java diff --git a/src/main/java/gg/essential/network/connectionmanager/queue/SequentialPacketQueue.java b/gui/essential/src/main/java/gg/essential/network/connectionmanager/queue/SequentialPacketQueue.java similarity index 78% rename from src/main/java/gg/essential/network/connectionmanager/queue/SequentialPacketQueue.java rename to gui/essential/src/main/java/gg/essential/network/connectionmanager/queue/SequentialPacketQueue.java index 9ed2d17..1d701b1 100644 --- a/src/main/java/gg/essential/network/connectionmanager/queue/SequentialPacketQueue.java +++ b/gui/essential/src/main/java/gg/essential/network/connectionmanager/queue/SequentialPacketQueue.java @@ -12,9 +12,13 @@ package gg.essential.network.connectionmanager.queue; import gg.essential.connectionmanager.common.packet.Packet; -import gg.essential.network.connectionmanager.ConnectionManager; -import gg.essential.util.Multithreading; +import gg.essential.network.CMConnection; import kotlin.Pair; +import kotlin.Unit; +import kotlin.coroutines.Continuation; +import kotlin.coroutines.CoroutineContext; +import kotlinx.coroutines.DelayKt; +import kotlinx.coroutines.Dispatchers; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -28,7 +32,7 @@ public class SequentialPacketQueue implements PacketQueue { private static final long ATTEMPT_RESEND_SECONDS = 1L; @NotNull - private final ConnectionManager connectionManager; + private final CMConnection cmConnection; @NotNull private final TimeoutPolicy timeoutPolicy; @@ -39,10 +43,10 @@ public class SequentialPacketQueue implements PacketQueue { private Pair>> waitingFor; public SequentialPacketQueue( - @NotNull ConnectionManager connectionManager, + @NotNull CMConnection cmConnection, @NotNull TimeoutPolicy timeoutPolicy ) { - this.connectionManager = connectionManager; + this.cmConnection = cmConnection; this.timeoutPolicy = timeoutPolicy; } @@ -66,10 +70,20 @@ private void process() { } private synchronized void attemptSend(@NotNull final Pair>> next) { - if (this.connectionManager.isOpen() && this.connectionManager.isAuthenticated()) { - this.connectionManager.send(next.getFirst(), resp -> handleResponse(next, resp)); + if (this.cmConnection.isOpen()) { + this.cmConnection.send(next.getFirst(), resp -> handleResponse(next, resp), TimeUnit.SECONDS, 10L); } else if (timeoutPolicy == TimeoutPolicy.RETRANSMIT) { - Multithreading.getScheduledPool().schedule(() -> attemptSend(next), ATTEMPT_RESEND_SECONDS, TimeUnit.SECONDS); + DelayKt.delay(ATTEMPT_RESEND_SECONDS * 1000, new Continuation() { + @Override + public @NotNull CoroutineContext getContext() { + return Dispatchers.getIO(); + } + + @Override + public void resumeWith(@NotNull Object o) { + attemptSend(next); + } + }); } else { handleResponse(next, Optional.empty()); } @@ -116,11 +130,11 @@ public enum TimeoutPolicy { } public static class Builder { - private final @NotNull ConnectionManager connectionManager; + private final @NotNull CMConnection cmConnection; private @NotNull TimeoutPolicy timeoutPolicy = TimeoutPolicy.RETRANSMIT; - public Builder(@NotNull ConnectionManager connectionManager) { - this.connectionManager = connectionManager; + public Builder(@NotNull CMConnection cmConnection) { + this.cmConnection = cmConnection; } public Builder onTimeoutRetransmit() { @@ -137,7 +151,7 @@ public Builder setTimeoutPolicy(@NotNull TimeoutPolicy timeoutPolicy) { } public SequentialPacketQueue create() { - return new SequentialPacketQueue(connectionManager, timeoutPolicy); + return new SequentialPacketQueue(cmConnection, timeoutPolicy); } } } diff --git a/src/main/java/gg/essential/network/connectionmanager/subscription/SubscriptionManager.java b/gui/essential/src/main/java/gg/essential/network/connectionmanager/subscription/SubscriptionManager.java similarity index 84% rename from src/main/java/gg/essential/network/connectionmanager/subscription/SubscriptionManager.java rename to gui/essential/src/main/java/gg/essential/network/connectionmanager/subscription/SubscriptionManager.java index 9ee361a..c256469 100644 --- a/src/main/java/gg/essential/network/connectionmanager/subscription/SubscriptionManager.java +++ b/gui/essential/src/main/java/gg/essential/network/connectionmanager/subscription/SubscriptionManager.java @@ -11,13 +11,12 @@ */ package gg.essential.network.connectionmanager.subscription; -import com.google.common.collect.Sets; import gg.essential.connectionmanager.common.packet.subscription.SubscriptionUpdatePacket; -import gg.essential.network.connectionmanager.ConnectionManager; +import gg.essential.network.CMConnection; import gg.essential.network.connectionmanager.NetworkedManager; import gg.essential.network.connectionmanager.queue.PacketQueue; import gg.essential.network.connectionmanager.queue.SequentialPacketQueue; -import gg.essential.util.UUIDUtil; +import gg.essential.util.USession; import org.jetbrains.annotations.NotNull; import java.util.*; @@ -29,16 +28,16 @@ public class SubscriptionManager implements NetworkedManager { private final List listeners = new ArrayList<>(); - private final Set subscriptions = Sets.newConcurrentHashSet(); + private final Set subscriptions = new HashSet<>(); - public SubscriptionManager(@NotNull ConnectionManager connectionManager) { - this.packetQueue = new SequentialPacketQueue.Builder(connectionManager) + public SubscriptionManager(@NotNull CMConnection cmConnection) { + this.packetQueue = new SequentialPacketQueue.Builder(cmConnection) .onTimeoutRetransmit() .create(); } public boolean isSubscribedOrSelf(@NotNull UUID uuid) { - return this.isSubscribed(uuid) || uuid.equals(UUIDUtil.getClientUUID()); + return this.isSubscribed(uuid) || uuid.equals(USession.Companion.activeNow().getUuid()); } public boolean isSubscribed(@NotNull UUID uuid) { @@ -46,7 +45,7 @@ public boolean isSubscribed(@NotNull UUID uuid) { } public void subscribeToFeeds(@NotNull Set uuids) { - uuids.remove(UUIDUtil.getClientUUID()); + uuids.remove(USession.Companion.activeNow().getUuid()); this.subscriptions.addAll(uuids); this.packetQueue.enqueue(new SubscriptionUpdatePacket(uuids.toArray(new UUID[0]), true), response -> { @@ -57,7 +56,7 @@ public void subscribeToFeeds(@NotNull Set uuids) { } public void unSubscribeFromFeeds(@NotNull Set uuids) { - uuids.remove(UUIDUtil.getClientUUID()); + uuids.remove(USession.Companion.activeNow().getUuid()); this.subscriptions.removeAll(uuids); this.packetQueue.enqueue(new SubscriptionUpdatePacket(uuids.toArray(new UUID[0]), false), response -> { diff --git a/src/main/kotlin/gg/essential/config/EssentialConfig.kt b/gui/essential/src/main/kotlin/gg/essential/config/EssentialConfig.kt similarity index 85% rename from src/main/kotlin/gg/essential/config/EssentialConfig.kt rename to gui/essential/src/main/kotlin/gg/essential/config/EssentialConfig.kt index 9ef3130..f8fa556 100644 --- a/src/main/kotlin/gg/essential/config/EssentialConfig.kt +++ b/gui/essential/src/main/kotlin/gg/essential/config/EssentialConfig.kt @@ -11,32 +11,15 @@ */ package gg.essential.config -import com.sparkuniverse.toolbox.relationships.enums.FriendRequestPrivacySetting -import gg.essential.Essential -import gg.essential.commands.EssentialCommandRegistry -import gg.essential.connectionmanager.common.packet.relationships.privacy.FriendRequestPrivacySettingPacket -import gg.essential.connectionmanager.common.packet.response.ResponseActionPacket -import gg.essential.data.OnboardingData -import gg.essential.data.OnboardingData.hasAcceptedTos -import gg.essential.elementa.components.Window import gg.essential.gui.common.modal.DangerConfirmationEssentialModal import gg.essential.gui.common.modal.configure import gg.essential.gui.elementa.state.v2.* import gg.essential.gui.elementa.state.v2.combinators.* -import gg.essential.gui.modal.discord.DiscordActivityStatusModal -import gg.essential.gui.modals.TOSModal -import gg.essential.gui.notification.Notifications -import gg.essential.gui.notification.error -import gg.essential.gui.notification.sendTosNotification -import gg.essential.gui.screenshot.ScreenshotPreviewActionSlot -import gg.essential.gui.vigilancev2.VigilanceV2SettingsGui -import gg.essential.util.AutoUpdate -import gg.essential.util.GuiUtil +import gg.essential.gui.screenshot.toast.ScreenshotPreviewActionSlot +import gg.essential.mod.vigilance2.Vigilant2 import gg.essential.util.GuiEssentialPlatform -import gg.essential.util.OptiFineUtil +import gg.essential.util.GuiEssentialPlatform.Companion.platform import gg.essential.vigilance.data.* -import net.minecraft.client.entity.EntityPlayerSP -import net.minecraft.entity.Entity import kotlin.reflect.KProperty object EssentialConfig : Vigilant2(), GuiEssentialPlatform.Config { @@ -160,21 +143,16 @@ object EssentialConfig : Vigilant2(), GuiEssentialPlatform.Config { var discordShowCurrentServer: Boolean by discordShowCurrentServerState private fun revokeTosButton() { - GuiUtil.pushModal { manager -> + val manager = platform.createModalManager() + manager.queueModal( DangerConfirmationEssentialModal(manager, "Confirm", false).configure { titleText = "Revoking Essential's Terms of Service and Privacy Policy will cause Essential features not to work. Are you sure you want to proceed?" }.onPrimaryAction { - revokeTos() + doRevokeTos() } - } - } - - private fun revokeTos() { - if (checkSPS()) { - OnboardingData.setDeniedTos() - Essential.getInstance().connectionManager.onTosRevokedOrEssentialDisabled() - } + ) } + lateinit var doRevokeTos: () -> Unit val disableCosmeticsInInventoryState = property("Cosmetics.General.Disable cosmetics in inventory", false) var disableCosmeticsInInventory: Boolean by disableCosmeticsInInventoryState @@ -593,7 +571,7 @@ object EssentialConfig : Vigilant2(), GuiEssentialPlatform.Config { description = "Shows the vanilla screenshot capture message in in-game chat." } } - if (!OptiFineUtil.isLoaded()) { + if (!platform.isOptiFineInstalled) { subcategory("Zoom") { switch(zoomSmoothCameraState) { name = "Smooth zoomed camera" @@ -648,104 +626,6 @@ object EssentialConfig : Vigilant2(), GuiEssentialPlatform.Config { } } - fun gui(): VigilanceV2SettingsGui = (gui)() // to keep existing use from Java the same - - private val referenceHolder = ReferenceHolderImpl() - - fun hookUp() { - friendRequestPrivacyState.onSetValue(referenceHolder) { it -> - if (hasAcceptedTos()) { - val connectionManager = Essential.getInstance().connectionManager - val privacy = FriendRequestPrivacySetting.values()[it] - - connectionManager.send(FriendRequestPrivacySettingPacket(privacy)) { - val get = it.orElse(null) - if (get == null || !(get is ResponseActionPacket && get.isSuccessful)) { - Notifications.error("Error", "An unexpected error occurred. Please try again.") - } - } - } - } - - ownCosmeticsHiddenStateWithSource.onSetValue(referenceHolder) { (hidden, setByUser) -> - if (Essential.getInstance().connectionManager.isAuthenticated) { - Essential.getInstance().connectionManager.cosmeticsManager.setOwnCosmeticVisibility(false, !hidden) - } else { - if (!setByUser) return@onSetValue // infra/mod may set whatever it wants, only the user is getting checked - if (hasAcceptedTos()) { - Notifications.error( - "Essential Network Error", - "Unable to establish connection with the Essential Network." - ) - } else { - fun showTOS() = GuiUtil.pushModal { TOSModal(it, unprompted = false, requiresAuth = true, {}) } - if (GuiUtil.openedScreen() == null) { - // Show a notification when we're not in any menu, so it's less intrusive - sendTosNotification { showTOS() } - } else { - showTOS() - } - } - this.ownCosmeticsHidden = !hidden - } - } - - discordRichPresenceState.onSetValue(referenceHolder) { enabled -> - if (!enabled) return@onSetValue - - GuiUtil.pushModal { DiscordActivityStatusModal(it) } - } - - essentialEnabledState.onSetValue(referenceHolder) { enabling -> - Window.enqueueRenderOperation { toggleEssential(enabling) } - } - - autoUpdate = AutoUpdate.autoUpdate.get() - autoUpdateState.onSetValue(referenceHolder) { shouldAutoUpdate -> - if (shouldAutoUpdate != AutoUpdate.autoUpdate.get()) { - // User explicitly changed the value - // Delayed to allow setAutoUpdate to confirm the value of the autoUpdate setting - Window.enqueueRenderOperation { - AutoUpdate.setAutoUpdates(shouldAutoUpdate) - } - } - } - } - - fun getCosmeticArmorSetting(entity: Entity): Int { - return if (entity is EntityPlayerSP) { - cosmeticArmorSettingSelf - } else { - cosmeticArmorSettingOther - } - } - - - private fun checkSPS(): Boolean { - return if (Essential.getInstance().connectionManager.spsManager.localSession != null) { - Notifications.error("Error", "You cannot disable Essential while hosting a world.") - false - } else true - } - - private fun toggleEssential(enabling: Boolean) { - // Trying to disable Essential while in an SPS world - if (!enabling && !checkSPS()) { - this.essentialEnabled = true - return - } - - this.essentialEnabled = enabling - - Essential.getInstance().keybindingRegistry.refreshBinds() - (Essential.getInstance().commandRegistry() as EssentialCommandRegistry).checkMiniCommands() - Essential.getInstance().checkListeners() - - if (!enabling) { - Essential.getInstance().connectionManager.onTosRevokedOrEssentialDisabled() - } - } - // These are intentionally not public/global because subscribing to updated a backing field might generally be // preferable, especially if State becomes lazy. private operator fun MutableState.getValue(essentialConfig: EssentialConfig, property: KProperty<*>): T { diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/EssentialPalette.kt b/gui/essential/src/main/kotlin/gg/essential/gui/EssentialPalette.kt index ebc557a..20a5c73 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/EssentialPalette.kt +++ b/gui/essential/src/main/kotlin/gg/essential/gui/EssentialPalette.kt @@ -11,6 +11,7 @@ */ package gg.essential.gui +import gg.essential.config.LoadsResources import gg.essential.elementa.components.UIImage import gg.essential.elementa.state.BasicState import gg.essential.elementa.state.State @@ -21,6 +22,7 @@ import gg.essential.gui.image.ResourceImageFactory import java.awt.Color import java.awt.image.BufferedImage import java.util.concurrent.CompletableFuture +import kotlin.random.Random object EssentialPalette { @@ -87,6 +89,9 @@ object EssentialPalette { @JvmField val LIGHT_DIVIDER: Color = Color(0x303030) + @JvmField + val DIVIDER: Color = Color(0x474747) + @JvmField val LIGHT_SCROLLBAR: Color = Color(0x555555) @@ -364,6 +369,61 @@ object EssentialPalette { @JvmField val PURPLE_BUTTON_HOVER: Color = Color(0x5947BF) + /** Outline Buttons **/ + @JvmField + val GREEN_OUTLINE_BUTTON: Color = Color(0x1D4728) + @JvmField + val GREEN_OUTLINE_BUTTON_OUTLINE: Color = Color(0x327B44) + @JvmField + val GREEN_OUTLINE_BUTTON_HOVER: Color = Color(0x276136) + @JvmField + val GREEN_OUTLINE_BUTTON_OUTLINE_HOVER: Color = Color(0x3E9252) + + @JvmField + val RED_OUTLINE_BUTTON: Color = Color(0x461F1F) + @JvmField + val RED_OUTLINE_BUTTON_OUTLINE: Color = Color(0x8B3636) + @JvmField + val RED_OUTLINE_BUTTON_HOVER: Color = Color(0x642626) + @JvmField + val RED_OUTLINE_BUTTON_OUTLINE_HOVER: Color = Color(0x9F4444) + + @JvmField + val BLUE_OUTLINE_BUTTON: Color = Color(0x223F69) + @JvmField + val BLUE_OUTLINE_BUTTON_OUTLINE: Color = Color(0x3671C7) + @JvmField + val BLUE_OUTLINE_BUTTON_HOVER: Color = Color(0x2A5695) + @JvmField + val BLUE_OUTLINE_BUTTON_OUTLINE_HOVER: Color = Color(0x5490E8) + + @JvmField + val GRAY_OUTLINE_BUTTON: Color = Color(0x323232) + @JvmField + val GRAY_OUTLINE_BUTTON_OUTLINE: Color = Color(0x5C5C5C) + @JvmField + val GRAY_OUTLINE_BUTTON_HOVER: Color = Color(0x474747) + @JvmField + val GRAY_OUTLINE_BUTTON_OUTLINE_HOVER: Color = Color(0x757575) + + @JvmField + val YELLOW_OUTLINE_BUTTON: Color = Color(0x583E14) + @JvmField + val YELLOW_OUTLINE_BUTTON_OUTLINE: Color = Color(0x8F621F) + @JvmField + val YELLOW_OUTLINE_BUTTON_HOVER: Color = Color(0x74521D) + @JvmField + val YELLOW_OUTLINE_BUTTON_OUTLINE_HOVER: Color = Color(0xBA8537) + + @JvmField + val PURPLE_OUTLINE_BUTTON: Color = Color(0x292063) + @JvmField + val PURPLE_OUTLINE_BUTTON_OUTLINE: Color = Color(0x5947BF) + @JvmField + val PURPLE_OUTLINE_BUTTON_HOVER: Color = Color(0x352A7A) + @JvmField + val PURPLE_OUTLINE_BUTTON_OUTLINE_HOVER: Color = Color(0x6F5CE5) + /** Gray/900 */ @JvmField val INVALID_SCREENSHOT_TEXT: Color = Color(0x999999) @@ -970,4 +1030,11 @@ object EssentialPalette { @JvmField val COIN_BUNDLE_0_999: ImageFactory = ResourceImageFactory("/assets/essential/textures/coin/coin_bundle_0_999.png") + @LoadsResources("/assets/essential/textures/friends/group_[a-z]+.png") + private fun createGroupIconFactory(name: String): ImageFactory = + ResourceImageFactory("/assets/essential/textures/friends/group_$name.png") + + val GROUP_ICONS_8X: List = listOf("blue", "purple", "red", "yellow").map(::createGroupIconFactory) + + fun groupIconForChannel(channelId: Long): ImageFactory = GROUP_ICONS_8X.random(Random(channelId)) } diff --git a/src/main/kotlin/gg/essential/gui/about/components/ColoredDivider.kt b/gui/essential/src/main/kotlin/gg/essential/gui/about/components/ColoredDivider.kt similarity index 100% rename from src/main/kotlin/gg/essential/gui/about/components/ColoredDivider.kt rename to gui/essential/src/main/kotlin/gg/essential/gui/about/components/ColoredDivider.kt diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/common/EssentialDropDown.kt b/gui/essential/src/main/kotlin/gg/essential/gui/common/EssentialDropDown.kt index c3e9031..db5c676 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/common/EssentialDropDown.kt +++ b/gui/essential/src/main/kotlin/gg/essential/gui/common/EssentialDropDown.kt @@ -205,6 +205,11 @@ class EssentialDropDown( } } + fun select(value: T) { + val option = items.getUntracked().find { it.value == value } ?: return + select(option) + } + fun select(option: Option) { if (items.get().contains(option)) { selectedOption.set(option) diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/common/input/AbstractTextInput.kt b/gui/essential/src/main/kotlin/gg/essential/gui/common/input/AbstractTextInput.kt index dba2af0..651c93f 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/common/input/AbstractTextInput.kt +++ b/gui/essential/src/main/kotlin/gg/essential/gui/common/input/AbstractTextInput.kt @@ -544,7 +544,7 @@ abstract class AbstractTextInput( return } - val addTextOperation = AddTextOperation(text, cursor) + val addTextOperation = AddTextOperation(text, selectionStart()) if (hasSelection()) { val removeTextOperation = RemoveTextOperation(selectionStart(), selectionEnd(), selectAfterUndo = true) diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/common/modal/EssentialModal2.kt b/gui/essential/src/main/kotlin/gg/essential/gui/common/modal/EssentialModal2.kt index c4f8201..e5efb0d 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/common/modal/EssentialModal2.kt +++ b/gui/essential/src/main/kotlin/gg/essential/gui/common/modal/EssentialModal2.kt @@ -242,10 +242,12 @@ abstract class EssentialModal2( modifier: Modifier = Modifier, style: State = stateOf(StyledButton.Style.GRAY), enableRetexturing: State = stateOf(false), + disabled: State = stateOf(false), action: suspend () -> Unit, content: LayoutScope.(style: State) -> Unit, ) { - val disabled = mutableStateOf(false) + val actionRunning = mutableStateOf(false) + val effectiveDisabled = State { disabled() || actionRunning() } styledButton( Modifier @@ -253,16 +255,16 @@ abstract class EssentialModal2( USound.playButtonPress() coroutineScope.launch { - disabled.set(true) + actionRunning.set(true) action() - disabled.set(false) + actionRunning.set(false) } } - .focusable(disabled) + .focusable(effectiveDisabled) .then(modifier), style, enableRetexturing, - disabled, + effectiveDisabled, content ) } @@ -272,6 +274,7 @@ abstract class EssentialModal2( modifier: Modifier = Modifier, style: StyledButton.Style, enableRetexturing: Boolean = false, + disabled: Boolean = false, action: suspend () -> Unit, content: LayoutScope.(style: State) -> Unit, ) { @@ -279,6 +282,7 @@ abstract class EssentialModal2( modifier, stateOf(style), stateOf(enableRetexturing), + stateOf(disabled), action, content, ) diff --git a/src/main/kotlin/gg/essential/gui/friends/message/MessageUtils.kt b/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/MessageUtils.kt similarity index 100% rename from src/main/kotlin/gg/essential/gui/friends/message/MessageUtils.kt rename to gui/essential/src/main/kotlin/gg/essential/gui/friends/message/MessageUtils.kt diff --git a/src/main/kotlin/gg/essential/gui/friends/message/v2/ClientMessage.kt b/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/v2/ClientMessage.kt similarity index 74% rename from src/main/kotlin/gg/essential/gui/friends/message/v2/ClientMessage.kt rename to gui/essential/src/main/kotlin/gg/essential/gui/friends/message/v2/ClientMessage.kt index c8023a3..6ab5534 100644 --- a/src/main/kotlin/gg/essential/gui/friends/message/v2/ClientMessage.kt +++ b/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/v2/ClientMessage.kt @@ -12,14 +12,13 @@ package gg.essential.gui.friends.message.v2 import com.sparkuniverse.toolbox.chat.model.Channel -import com.sparkuniverse.toolbox.chat.model.Message -import gg.essential.Essential import gg.essential.cosmetics.CosmeticId import gg.essential.gui.friends.message.MessageUtils import gg.essential.gui.friends.message.MessageUtils.handleMarkdownUrls import gg.essential.mod.Model import gg.essential.mod.Skin import okhttp3.HttpUrl +import org.slf4j.LoggerFactory import java.net.MalformedURLException import java.net.URL import java.time.Instant @@ -57,7 +56,7 @@ data class ClientMessage( add(Part.Image(URL(match))) stripUrl(match) } catch (e: MalformedURLException) { - Essential.logger.debug("Ignoring invalid URL:", e) + LOGGER.debug("Ignoring invalid URL:", e) } } @@ -82,18 +81,6 @@ data class ClientMessage( } } - fun getInfraInstance(): Message { - return Essential.getInstance().connectionManager.chatManager.getMessageById(id) ?: Message( - id, - channel.id, - sender, - contents, - true, // So the social menu doesn't try to mark this message as read - replyTo?.messageId, - lastEditTime, - ) - } - sealed interface Part { data class Text(val content: String) : Part data class Image(val url: URL) : Part @@ -102,25 +89,7 @@ data class ClientMessage( } companion object { - - /** - * Creates a message from a network type, excluding the replyTo state. - */ - fun fromNetwork(message: Message): ClientMessage { - val chatManager = Essential.getInstance().connectionManager.chatManager - return ClientMessage( - message.id, - chatManager.getChannel(message.channelId).get(), - message.sender, - message.contents, - SendState.CONFIRMED, - message.replyTargetId?.let { - MessageRef(message.channelId, it) - }, - message.lastEditTime, - ) - } - + private val LOGGER = LoggerFactory.getLogger(ClientMessage::class.java) } } diff --git a/src/main/kotlin/gg/essential/gui/friends/message/v2/MessageRef.kt b/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/v2/MessageRef.kt similarity index 94% rename from src/main/kotlin/gg/essential/gui/friends/message/v2/MessageRef.kt rename to gui/essential/src/main/kotlin/gg/essential/gui/friends/message/v2/MessageRef.kt index d9292ca..769c3c2 100644 --- a/src/main/kotlin/gg/essential/gui/friends/message/v2/MessageRef.kt +++ b/gui/essential/src/main/kotlin/gg/essential/gui/friends/message/v2/MessageRef.kt @@ -15,9 +15,10 @@ import com.sparkuniverse.toolbox.chat.enums.ChannelType import com.sparkuniverse.toolbox.chat.model.Channel import com.sparkuniverse.toolbox.chat.model.CreatedInfo import com.sparkuniverse.toolbox.util.DateTime -import gg.essential.Essential import gg.essential.elementa.state.BasicState import gg.essential.gui.common.WeakState +import gg.essential.util.GuiEssentialPlatform +import gg.essential.util.GuiEssentialPlatform.Companion.platform import java.util.* /** @@ -53,7 +54,7 @@ data class MessageRef( */ fun eagerlyLoad() { if (messageId != -1L && !isInitialized()) { - Essential.getInstance().connectionManager.chatManager.retrieveChannelHistoryUntil(this) + platform.resolveMessageRef(this) } } diff --git a/src/main/kotlin/gg/essential/gui/friends/state/SocialStates.kt b/gui/essential/src/main/kotlin/gg/essential/gui/friends/state/SocialStates.kt similarity index 97% rename from src/main/kotlin/gg/essential/gui/friends/state/SocialStates.kt rename to gui/essential/src/main/kotlin/gg/essential/gui/friends/state/SocialStates.kt index 2c28e8f..3d5bff1 100644 --- a/src/main/kotlin/gg/essential/gui/friends/state/SocialStates.kt +++ b/gui/essential/src/main/kotlin/gg/essential/gui/friends/state/SocialStates.kt @@ -15,7 +15,6 @@ import com.sparkuniverse.toolbox.chat.model.Channel import com.sparkuniverse.toolbox.chat.model.Message import gg.essential.elementa.state.State import gg.essential.elementa.utils.ObservableList -import gg.essential.gui.common.ReadOnlyState import gg.essential.gui.elementa.state.v2.ListState import gg.essential.gui.friends.message.v2.ClientMessage import gg.essential.gui.elementa.state.v2.MutableState as MutableStateV2 @@ -25,6 +24,12 @@ import java.time.Instant import java.util.* import java.util.concurrent.CompletableFuture +interface SocialStates { + val relationships: IRelationshipStates + val messages: IMessengerStates + val activity: IStatusStates +} + interface IRelationshipStates { fun getObservableFriendList(): ObservableList @@ -212,7 +217,7 @@ interface IMessengerManager { interface IStatusStates { - fun getActivityState(uuid: UUID): ReadOnlyState + fun getActivityState(uuid: UUID): StateV2 fun getActivity(uuid: UUID): PlayerActivity diff --git a/src/main/kotlin/gg/essential/gui/image/EssentialAssetImageFactory.kt b/gui/essential/src/main/kotlin/gg/essential/gui/image/EssentialAssetImageFactory.kt similarity index 91% rename from src/main/kotlin/gg/essential/gui/image/EssentialAssetImageFactory.kt rename to gui/essential/src/main/kotlin/gg/essential/gui/image/EssentialAssetImageFactory.kt index ba70f40..5d6a2da 100644 --- a/src/main/kotlin/gg/essential/gui/image/EssentialAssetImageFactory.kt +++ b/gui/essential/src/main/kotlin/gg/essential/gui/image/EssentialAssetImageFactory.kt @@ -11,10 +11,10 @@ */ package gg.essential.gui.image -import gg.essential.Essential import gg.essential.elementa.components.UIImage import gg.essential.mod.EssentialAsset import gg.essential.network.connectionmanager.cosmetics.AssetLoader +import gg.essential.util.GuiEssentialPlatform.Companion.platform import java.io.ByteArrayInputStream import java.util.concurrent.CompletableFuture import javax.imageio.ImageIO @@ -26,7 +26,10 @@ data class EssentialAssetImageFactory( private val asset: EssentialAsset, private val priority: AssetLoader.Priority = AssetLoader.Priority.Blocking, ) : ImageFactory() { - private val assetLoader = Essential.getInstance().connectionManager.cosmeticsManager.assetLoader + private val assetLoader = platform.assetLoader + + override val name: String + get() = asset.url fun primeCache(cachePriority: AssetLoader.Priority) { getCachedImage(cachePriority) diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/image/ImageFactory.kt b/gui/essential/src/main/kotlin/gg/essential/gui/image/ImageFactory.kt index 19d6164..5f167ac 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/image/ImageFactory.kt +++ b/gui/essential/src/main/kotlin/gg/essential/gui/image/ImageFactory.kt @@ -41,6 +41,9 @@ data class ImageGeneratorSettings( abstract class ImageFactory( protected val settings: ImageGeneratorSettings = ImageGeneratorSettings() ) { + /** A human readable name for the image produced. Used for debugging. */ + abstract val name: String + /** * Produces a new [UIImage] and applies [settings] */ @@ -90,6 +93,8 @@ private class DelegatedImageImageFactory( settings: ImageGeneratorSettings ) : ImageFactory(settings) { + override val name: String by innerSupplier::name + override fun generate(): UIImage { return innerSupplier.create() } @@ -105,8 +110,13 @@ private class DelegatedImageImageFactory( * Does not support caching. */ fun ImageFactory( + name: String = "", generator: () -> UIImage, ): ImageFactory = object : ImageFactory() { + + override val name: String + get() = name.ifEmpty { generator.toString() } + override fun generate(): UIImage { return generator() } diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/image/ResourceImageFactory.kt b/gui/essential/src/main/kotlin/gg/essential/gui/image/ResourceImageFactory.kt index 224c6e8..450360b 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/image/ResourceImageFactory.kt +++ b/gui/essential/src/main/kotlin/gg/essential/gui/image/ResourceImageFactory.kt @@ -14,6 +14,7 @@ package gg.essential.gui.image import gg.essential.config.LoadsResources import gg.essential.elementa.components.UIImage import gg.essential.elementa.utils.ResourceCache +import java.util.* /** * An ImageFactory that returns images resulting from the specified [resource]. @@ -26,17 +27,38 @@ class ResourceImageFactory @LoadsResources("%resource%") constructor( init { if (preload) { - generate() + synchronized(preloadQueue) { + if (!preloadEnabled) { + preloadQueue.add(this) + } else { + generate() + } + } } } + override val name: String + get() = resource + override fun generate(): UIImage { return UIImage.ofResourceCached(resource, cache) } - private companion object { + companion object { + private var preloadEnabled = false + private val preloadQueue = Collections.newSetFromMap(WeakHashMap()) + + fun preload() { + synchronized(preloadQueue) { + preloadEnabled = true + for (factory in preloadQueue) { + factory.generate() + } + preloadQueue.clear() + } + } - val cache: ResourceCache = ResourceCache(Int.MAX_VALUE) + private val cache: ResourceCache = ResourceCache(Int.MAX_VALUE) } } diff --git a/src/main/kotlin/gg/essential/gui/modals/select/SelectModal.kt b/gui/essential/src/main/kotlin/gg/essential/gui/modals/select/SelectModal.kt similarity index 99% rename from src/main/kotlin/gg/essential/gui/modals/select/SelectModal.kt rename to gui/essential/src/main/kotlin/gg/essential/gui/modals/select/SelectModal.kt index 60b1d2f..3c9099a 100644 --- a/src/main/kotlin/gg/essential/gui/modals/select/SelectModal.kt +++ b/gui/essential/src/main/kotlin/gg/essential/gui/modals/select/SelectModal.kt @@ -11,7 +11,6 @@ */ package gg.essential.gui.modals.select -import gg.essential.api.gui.GuiRequiresTOS import gg.essential.elementa.UIComponent import gg.essential.elementa.components.GradientComponent import gg.essential.elementa.components.UIContainer @@ -64,7 +63,7 @@ class SelectModal( private val initiallySelected: Set = setOf(), whenEmpty: (LayoutScope.() -> Unit)? = null, extraContent: (LayoutScope.() -> Unit)? = null, -) : SearchableConfirmDenyModal(modalManager, requiresButtonPress, fadeTime), GuiRequiresTOS { +) : SearchableConfirmDenyModal(modalManager, requiresButtonPress, fadeTime) { private val selectedStates = mutableMapOf>() private val filteredStates = mutableMapOf>() diff --git a/src/main/kotlin/gg/essential/gui/modals/select/builder.kt b/gui/essential/src/main/kotlin/gg/essential/gui/modals/select/builder.kt similarity index 91% rename from src/main/kotlin/gg/essential/gui/modals/select/builder.kt rename to gui/essential/src/main/kotlin/gg/essential/gui/modals/select/builder.kt index a0de84f..537aa0e 100644 --- a/src/main/kotlin/gg/essential/gui/modals/select/builder.kt +++ b/gui/essential/src/main/kotlin/gg/essential/gui/modals/select/builder.kt @@ -13,7 +13,6 @@ package gg.essential.gui.modals.select import com.sparkuniverse.toolbox.chat.enums.ChannelType import com.sparkuniverse.toolbox.chat.model.Channel -import gg.essential.Essential import gg.essential.elementa.state.toConstraint import gg.essential.gui.EssentialPalette import gg.essential.gui.common.IconButton @@ -36,22 +35,19 @@ import gg.essential.gui.elementa.state.v2.stateOf import gg.essential.gui.elementa.state.v2.toListState import gg.essential.gui.elementa.state.v2.toV1 import gg.essential.gui.elementa.state.v2.toV2 -import gg.essential.gui.friends.state.MessengerStateManagerImpl import gg.essential.gui.friends.state.PlayerActivity -import gg.essential.gui.friends.state.RelationshipStateManagerImpl -import gg.essential.gui.friends.state.StatusStateManagerImpl import gg.essential.gui.image.ImageFactory import gg.essential.gui.layoutdsl.* -import gg.essential.gui.modals.select.component.groupIcon import gg.essential.gui.modals.select.component.playerAvatar import gg.essential.gui.modals.select.component.playerName +import gg.essential.gui.overlay.ModalFlow import gg.essential.gui.overlay.ModalManager import gg.essential.universal.USound -import gg.essential.util.UUIDUtil -import gg.essential.util.essentialUriListener -import gg.essential.util.getOtherUser import gg.essential.gui.util.hoveredState import gg.essential.gui.util.toStateV2List +import gg.essential.util.GuiEssentialPlatform.Companion.platform +import gg.essential.util.USession +import gg.essential.util.UuidNameLookup import gg.essential.vigilance.utils.onLeftClick import java.awt.Color import java.util.* @@ -75,10 +71,10 @@ data class Section( class SelectModalBuilder( val title: String, ) { - private val connectionManager = Essential.getInstance().connectionManager - private val relationshipStates = RelationshipStateManagerImpl(connectionManager.relationshipManager) - private val messageStates = MessengerStateManagerImpl(connectionManager.chatManager) - private val statusStates = StatusStateManagerImpl(connectionManager.profileManager, connectionManager.spsManager) + private val socialStates by lazy { platform.createSocialStates() } + private val relationshipStates by lazy { socialStates.relationships } + private val messageStates by lazy { socialStates.messages } + private val statusStates by lazy { socialStates.activity } private val sections = mutableListOf>() internal val defaultUserRow: SectionLayoutBlock @@ -107,7 +103,9 @@ class SelectModalBuilder( internal val defaultUserOrGroupRow: SectionLayoutBlock get() = { selected, channel -> - val otherUser = channel.getOtherUser() + val otherUser = + if (channel.type != ChannelType.DIRECT_MESSAGE) null + else channel.members.find { it != USession.activeNow().uuid } row(Modifier.fillParent(padding = 3f)) { if (otherUser == null) { groupEntry(selected, channel.id) @@ -168,7 +166,7 @@ class SelectModalBuilder( whenEmpty = { column { spacer(height = 3f) - EssentialMarkdown(text, emptyTextMarkdownConfig)(Modifier.width(130f)).onLinkClicked(essentialUriListener) + EssentialMarkdown(text, emptyTextMarkdownConfig)(Modifier.width(130f)).onLinkClicked(platform.essentialUriListener) } } } @@ -184,7 +182,7 @@ class SelectModalBuilder( fun LayoutScope.groupEntry(selected: MutableState, id: Long) { row(Modifier.fillRemainingWidth(), Arrangement.spacedBy(5f)) { - groupIcon(id, modifier = Modifier.width(8f).heightAspect(1f)) + icon(EssentialPalette.groupIconForChannel(id)) row(Modifier.fillRemainingWidth(), Arrangement.spacedBy(float = FloatPosition.START)) { text(messageStates.getTitle(id), truncateIfTooSmall = true) } @@ -223,7 +221,7 @@ class SelectModalBuilder( map = map, identifiers = state, searchFilter = { identifier, searchText -> - UUIDUtil.nameState(identifier).contains(searchText, ignoreCase = true) + UuidNameLookup.nameState(identifier).contains(searchText, ignoreCase = true) }, block = block ) @@ -357,7 +355,7 @@ class SelectModalBuilder( val mappedFriends = relationshipStates.getObservableFriendList() .toStateV2List() .mapEach { uuid -> - uuid to statusStates.getActivityState(uuid).map { predicate(it) }.toV2() + uuid to statusStates.getActivityState(uuid).map { predicate(it) } } return stateBy { @@ -400,3 +398,19 @@ fun selectModal( ) = SelectModalBuilder(title) .apply(block) .build(modalManager) + +/** + * @param title: The title text for your modal + */ +suspend fun ModalFlow.selectModal( + title: String, + block: SelectModalBuilder.() -> Unit = {} +): Set? = awaitModal { continuation -> + SelectModalBuilder(title) + .modalSettings { + onPrimaryAction { result -> replaceWith(continuation.resumeImmediately(result)) } + onCancel { button -> if (button) replaceWith(continuation.resumeImmediately(null)) } + } + .apply(block) + .build(modalManager) +} diff --git a/src/main/kotlin/gg/essential/gui/modals/select/component/friend.kt b/gui/essential/src/main/kotlin/gg/essential/gui/modals/select/component/friend.kt similarity index 91% rename from src/main/kotlin/gg/essential/gui/modals/select/component/friend.kt rename to gui/essential/src/main/kotlin/gg/essential/gui/modals/select/component/friend.kt index c4d50bc..9c4fc11 100644 --- a/src/main/kotlin/gg/essential/gui/modals/select/component/friend.kt +++ b/gui/essential/src/main/kotlin/gg/essential/gui/modals/select/component/friend.kt @@ -17,7 +17,7 @@ import gg.essential.gui.layoutdsl.LayoutScope import gg.essential.gui.layoutdsl.Modifier import gg.essential.gui.layoutdsl.text import gg.essential.util.CachedAvatarImage -import gg.essential.util.UUIDUtil +import gg.essential.util.UuidNameLookup import java.util.* fun LayoutScope.playerAvatar(uuid: UUID, modifier: Modifier = Modifier) { @@ -28,5 +28,5 @@ fun LayoutScope.playerAvatar(uuid: UUID, modifier: Modifier = Modifier) { } fun LayoutScope.playerName(uuid: UUID, modifier: Modifier = Modifier) { - text(UUIDUtil.getNameAsState(uuid, "Loading..."), modifier) + text(UuidNameLookup.nameState(uuid, "Loading..."), modifier) } diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/notification/Notifications.kt b/gui/essential/src/main/kotlin/gg/essential/gui/notification/Notifications.kt new file mode 100644 index 0000000..4c4556b --- /dev/null +++ b/gui/essential/src/main/kotlin/gg/essential/gui/notification/Notifications.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.gui.notification + +import gg.essential.api.gui.NotificationBuilder +import gg.essential.api.gui.Notifications +import gg.essential.util.GuiEssentialPlatform.Companion.platform + +object Notifications : NotificationsManager by platform.notifications + +interface NotificationsManager : Notifications { + fun pushPersistentToast( + title: String, + message: String, + action: () -> Unit, + close: () -> Unit, + configure: NotificationBuilder.() -> Unit + ) + + fun removeNotificationById(id: Any) + + fun hasActiveNotifications(): Boolean + + fun hide() + fun show() +} diff --git a/src/main/kotlin/gg/essential/gui/notification/extensions.kt b/gui/essential/src/main/kotlin/gg/essential/gui/notification/extensions.kt similarity index 100% rename from src/main/kotlin/gg/essential/gui/notification/extensions.kt rename to gui/essential/src/main/kotlin/gg/essential/gui/notification/extensions.kt diff --git a/src/main/kotlin/gg/essential/gui/notification/helpers.kt b/gui/essential/src/main/kotlin/gg/essential/gui/notification/helpers.kt similarity index 100% rename from src/main/kotlin/gg/essential/gui/notification/helpers.kt rename to gui/essential/src/main/kotlin/gg/essential/gui/notification/helpers.kt diff --git a/src/main/kotlin/gg/essential/gui/notification/toastButton.kt b/gui/essential/src/main/kotlin/gg/essential/gui/notification/toastButton.kt similarity index 100% rename from src/main/kotlin/gg/essential/gui/notification/toastButton.kt rename to gui/essential/src/main/kotlin/gg/essential/gui/notification/toastButton.kt diff --git a/src/main/kotlin/gg/essential/gui/overlay/EphemeralLayer.kt b/gui/essential/src/main/kotlin/gg/essential/gui/overlay/EphemeralLayer.kt similarity index 100% rename from src/main/kotlin/gg/essential/gui/overlay/EphemeralLayer.kt rename to gui/essential/src/main/kotlin/gg/essential/gui/overlay/EphemeralLayer.kt diff --git a/src/main/kotlin/gg/essential/gui/overlay/Layer.kt b/gui/essential/src/main/kotlin/gg/essential/gui/overlay/Layer.kt similarity index 96% rename from src/main/kotlin/gg/essential/gui/overlay/Layer.kt rename to gui/essential/src/main/kotlin/gg/essential/gui/overlay/Layer.kt index bc28b68..83a2a88 100644 --- a/src/main/kotlin/gg/essential/gui/overlay/Layer.kt +++ b/gui/essential/src/main/kotlin/gg/essential/gui/overlay/Layer.kt @@ -13,7 +13,6 @@ package gg.essential.gui.overlay import gg.essential.elementa.ElementaVersion import gg.essential.elementa.components.Window -import gg.essential.gui.common.modal.Modal open class Layer(val priority: LayerPriority) { /** @@ -56,7 +55,8 @@ open class Layer(val priority: LayerPriority) { */ var unlocksMouse = priority == LayerPriority.Modal - internal var passThroughEvent = false + /** Internal. For OverlayManagerImpl only. */ + var passThroughEvent = false init { window.onKeyType { _, _ -> passThroughEvent = true } } diff --git a/src/main/kotlin/gg/essential/gui/overlay/LayerPriority.kt b/gui/essential/src/main/kotlin/gg/essential/gui/overlay/LayerPriority.kt similarity index 100% rename from src/main/kotlin/gg/essential/gui/overlay/LayerPriority.kt rename to gui/essential/src/main/kotlin/gg/essential/gui/overlay/LayerPriority.kt diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/overlay/ModalFlow.kt b/gui/essential/src/main/kotlin/gg/essential/gui/overlay/ModalFlow.kt new file mode 100644 index 0000000..6a87032 --- /dev/null +++ b/gui/essential/src/main/kotlin/gg/essential/gui/overlay/ModalFlow.kt @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.gui.overlay + +import gg.essential.gui.common.modal.Modal +import gg.essential.gui.layoutdsl.LayoutScope +import gg.essential.gui.overlay.ModalFlow.ModalContinuation +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.job +import kotlinx.coroutines.launch +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlin.coroutines.Continuation +import kotlin.coroutines.coroutineContext +import kotlin.coroutines.resume + +/** + * Manages a dynamic sequence of modals. + * @see launchModalFlow + */ +class ModalFlow(val modalManager: ModalManager) { + var replacePreviousModalWith: CompletableDeferred = CompletableDeferred(null) + + suspend fun awaitModal(block: (continuation: ModalContinuation) -> Modal): T { + val (deferred, result) = suspendCancellableCoroutine { continuation -> + val modal = block(ModalContinuation(modalManager, continuation)) + continuation.invokeOnCancellation { + modal.close() + } + replacePreviousModalWith.complete(modal) + } + replacePreviousModalWith = deferred + return result + } + + class ModalContinuation( + private val modalManager: ModalManager, + private val coroutineContinuation: Continuation, T>>, + ) { + /** + * Resumes the [ModalFlow] coroutine and suspends until the next modal is queued via [awaitModal]. + * + * If you cannot suspend, you may instead use [resumeImmediately] which will return an empty temporary [Modal] + * immediately and then later replace it with the real one once that has been determined. + */ + suspend fun resume(result: T): Modal? { + val job = CompletableDeferred(parent = coroutineContext.job) + coroutineContinuation.resume(Pair(job, result)) + return job.await() + } + + fun resumeImmediately(result: T): Modal { + return object : Modal(modalManager) { + override fun onOpen() { + super.onOpen() + + coroutineScope.launch { + replaceWith(resume(result)) + } + } + override fun LayoutScope.layoutModal() {} + override fun handleEscapeKeyPress() {} + } + } + } +} + diff --git a/src/main/kotlin/gg/essential/gui/overlay/OverlayManager.kt b/gui/essential/src/main/kotlin/gg/essential/gui/overlay/OverlayManager.kt similarity index 100% rename from src/main/kotlin/gg/essential/gui/overlay/OverlayManager.kt rename to gui/essential/src/main/kotlin/gg/essential/gui/overlay/OverlayManager.kt diff --git a/src/main/kotlin/gg/essential/gui/overlay/PersistentLayer.kt b/gui/essential/src/main/kotlin/gg/essential/gui/overlay/PersistentLayer.kt similarity index 100% rename from src/main/kotlin/gg/essential/gui/overlay/PersistentLayer.kt rename to gui/essential/src/main/kotlin/gg/essential/gui/overlay/PersistentLayer.kt diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/overlay/UIContainerModalManagerImpl.kt b/gui/essential/src/main/kotlin/gg/essential/gui/overlay/UIContainerModalManagerImpl.kt index c405615..a49ba0f 100644 --- a/gui/essential/src/main/kotlin/gg/essential/gui/overlay/UIContainerModalManagerImpl.kt +++ b/gui/essential/src/main/kotlin/gg/essential/gui/overlay/UIContainerModalManagerImpl.kt @@ -21,7 +21,7 @@ import gg.essential.elementa.dsl.provideDelegate import gg.essential.elementa.utils.withAlpha import gg.essential.gui.common.modal.Modal import gg.essential.gui.common.modal.defaultEssentialModalFadeTime -import gg.essential.gui.elementa.transitions.FadeInTransition +import gg.essential.gui.transitions.FadeInTransition import gg.essential.util.Client import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/screenshot/toast/ScreenshotPreviewAction.kt b/gui/essential/src/main/kotlin/gg/essential/gui/screenshot/toast/ScreenshotPreviewAction.kt new file mode 100644 index 0000000..7345319 --- /dev/null +++ b/gui/essential/src/main/kotlin/gg/essential/gui/screenshot/toast/ScreenshotPreviewAction.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.gui.screenshot.toast + +// The order of these actions is used/replicated in the settings. +// Remember to change in both places at once +enum class ScreenshotPreviewAction(val displayName: String) { + + COPY_PICTURE("Copy Picture"), + COPY_LINK("Copy Link"), + FAVORITE("Favorite"), + DELETE("Delete"), + SHARE("Share to Friends"), + EDIT("Edit"), + ; + +} \ No newline at end of file diff --git a/gui/essential/src/main/kotlin/gg/essential/gui/screenshot/toast/ScreenshotPreviewActionSlot.kt b/gui/essential/src/main/kotlin/gg/essential/gui/screenshot/toast/ScreenshotPreviewActionSlot.kt new file mode 100644 index 0000000..8bf5e83 --- /dev/null +++ b/gui/essential/src/main/kotlin/gg/essential/gui/screenshot/toast/ScreenshotPreviewActionSlot.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.gui.screenshot.toast + +import gg.essential.config.EssentialConfig + +enum class ScreenshotPreviewActionSlot(val defaultAction: ScreenshotPreviewAction) { + + TOP_LEFT(ScreenshotPreviewAction.EDIT), + TOP_RIGTH(ScreenshotPreviewAction.FAVORITE), + BOTTOM_LEFT(ScreenshotPreviewAction.COPY_PICTURE), + BOTTOM_RIGHT(ScreenshotPreviewAction.SHARE), + ; + + val action: ScreenshotPreviewAction + get() { + return ScreenshotPreviewAction.values().getOrNull( + when (this) { + TOP_LEFT -> EssentialConfig.screenshotOverlayTopLeftAction + TOP_RIGTH -> EssentialConfig.screenshotOverlayTopRightAction + BOTTOM_LEFT -> EssentialConfig.screenshotOverlayBottomLeftAction + BOTTOM_RIGHT -> EssentialConfig.screenshotOverlayBottomRightAction + } + ) ?: defaultAction + } + +} \ No newline at end of file diff --git a/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/coins/CoinBundle.kt b/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/coins/CoinBundle.kt new file mode 100644 index 0000000..f90d5a6 --- /dev/null +++ b/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/coins/CoinBundle.kt @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.network.connectionmanager.coins + +import gg.essential.gui.image.EssentialAssetImageFactory +import gg.essential.mod.EssentialAsset +import gg.essential.network.connectionmanager.cosmetics.AssetLoader +import gg.essential.util.format +import java.util.* +import kotlin.math.pow +import kotlin.math.round + +data class CoinBundle( + val id: String, + val numberOfCoins: Int, + val currency: Currency, + val price: Double, + val extraPercent: Int, + val iconAsset: EssentialAsset, + val isHighlighted: Boolean, + val isExchangeBundle: Boolean, + val isSpecificAmount: Boolean = false, +) { + + val iconFactory = EssentialAssetImageFactory(iconAsset) + + init { + iconFactory.primeCache(AssetLoader.Priority.Low) + } + + val formattedPrice: String = currency.format(price) + + fun getBundleForNumberOfCoins(coins: Int): CoinBundle { + val precision = 10.0.pow(currency.defaultFractionDigits) + return copy( + numberOfCoins = coins, + price = round((price / numberOfCoins) * coins * precision) / precision, + isSpecificAmount = true + ) + } + +} diff --git a/src/main/kotlin/gg/essential/network/connectionmanager/cosmetics/InfraCosmeticsData.kt b/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/cosmetics/InfraCosmeticsData.kt similarity index 82% rename from src/main/kotlin/gg/essential/network/connectionmanager/cosmetics/InfraCosmeticsData.kt rename to gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/cosmetics/InfraCosmeticsData.kt index 2c3cd66..21d2d6c 100644 --- a/src/main/kotlin/gg/essential/network/connectionmanager/cosmetics/InfraCosmeticsData.kt +++ b/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/cosmetics/InfraCosmeticsData.kt @@ -12,8 +12,11 @@ package gg.essential.network.connectionmanager.cosmetics import gg.essential.connectionmanager.common.packet.cosmetic.ClientCosmeticRequestPacket +import gg.essential.connectionmanager.common.packet.cosmetic.ServerCosmeticsPopulatePacket import gg.essential.connectionmanager.common.packet.cosmetic.categories.ClientCosmeticCategoriesRequestPacket +import gg.essential.connectionmanager.common.packet.cosmetic.categories.ServerCosmeticCategoriesPopulatePacket import gg.essential.connectionmanager.common.packet.wardrobe.ClientWardrobeStoreBundleRequestPacket +import gg.essential.connectionmanager.common.packet.wardrobe.ServerWardrobeStoreBundlePacket import gg.essential.cosmetics.CosmeticBundleId import gg.essential.cosmetics.CosmeticCategoryId import gg.essential.cosmetics.CosmeticId @@ -22,31 +25,32 @@ import gg.essential.gui.elementa.state.v2.add import gg.essential.gui.elementa.state.v2.clear import gg.essential.gui.elementa.state.v2.set import gg.essential.mod.EssentialAsset +import gg.essential.mod.cosmetics.CosmeticAssets import gg.essential.mod.cosmetics.settings.CosmeticProperty import gg.essential.mod.cosmetics.CosmeticSlot import gg.essential.mod.cosmetics.CosmeticType import gg.essential.mod.cosmetics.featured.FeaturedItem -import gg.essential.network.connectionmanager.ConnectionManager +import gg.essential.network.CMConnection import gg.essential.network.cosmetics.toMod -import gg.essential.network.cosmetics.toMod -import gg.essential.util.executor +import gg.essential.util.Client import gg.essential.util.logExceptions +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.asExecutor +import kotlinx.coroutines.launch import kotlinx.serialization.json.Json -import net.minecraft.client.Minecraft import java.time.Duration import java.time.Instant import java.util.concurrent.CompletableFuture -import java.util.concurrent.TimeUnit import gg.essential.cosmetics.model.Cosmetic as InfraCosmetic import gg.essential.cosmetics.model.CosmeticCategory as InfraCategory import gg.essential.cosmetics.model.CosmeticType as InfraType class InfraCosmeticsData private constructor( - private val connectionManager: ConnectionManager, + private val cmConnection: CMConnection, private val assetLoader: AssetLoader, private val state: MutableCosmeticsData, ) : CosmeticsData by state { - constructor(connectionManager: ConnectionManager, assetLoader: AssetLoader) : this(connectionManager, assetLoader, MutableCosmeticsData()) + constructor(cmConnection: CMConnection, assetLoader: AssetLoader) : this(cmConnection, assetLoader, MutableCosmeticsData()) private val categoriesKnownOrRequested = mutableSetOf() private val activeCategoryRequests = mutableMapOf() @@ -113,13 +117,13 @@ class InfraCosmeticsData private constructor( cosmeticsKnownOrRequested.add(infraCosmetic.id) activeCosmeticRequests.remove(infraCosmetic.id) - val assets = infraCosmetic.assets?.toMod(infraCosmetic.assetsMap?.mapValues { it.value.toMod() }) ?: return + val assets = CosmeticAssets(infraCosmetic.assetsMap.mapValues { it.value.toMod() }) val settingsAsset = assets.settings val settingsFuture = if (settingsAsset != null) { cosmeticsLoading[infraCosmetic.id] = Instant.now() assetLoader.getAssetBytes(settingsAsset, AssetLoader.Priority.Blocking) .thenApplyAsync { CosmeticProperty.fromJsonArray(String(it)) } - .whenCompleteAsync({ _, _ -> cosmeticsLoading.remove(infraCosmetic.id) }, Minecraft.getMinecraft().executor) + .whenCompleteAsync({ _, _ -> cosmeticsLoading.remove(infraCosmetic.id) }, Dispatchers.Client.asExecutor()) .logExceptions() } else { CompletableFuture.completedFuture(emptyList()) @@ -138,7 +142,7 @@ class InfraCosmeticsData private constructor( } requestCategoriesIfMissing(cosmetic.categories.keys) - }, Minecraft.getMinecraft().executor) + }, Dispatchers.Client.asExecutor()) } fun addBundle(infraBundle: CosmeticStoreBundle) { @@ -176,7 +180,7 @@ class InfraCosmeticsData private constructor( state.featuredPageCollections.add(collection) } }, - Minecraft.getMinecraft().executor + Dispatchers.Client.asExecutor() ).logExceptions() } @@ -189,15 +193,15 @@ class InfraCosmeticsData private constructor( activeCategoryRequests[id] = Instant.now() } - connectionManager.send( - ClientCosmeticCategoriesRequestPacket(unknownIds.toSet(), null, null), - { _ -> - for (id in unknownIds) { - activeCategoryRequests.remove(id) - } - }, - TimeUnit.SECONDS, CosmeticsManager.LOAD_TIMEOUT_SECONDS, - ) + cmConnection.connectionScope.launch { + cmConnection.call(ClientCosmeticCategoriesRequestPacket(unknownIds.toSet(), null, null)) + .exponentialBackoff() + .await() + + for (id in unknownIds) { + activeCategoryRequests.remove(id) + } + } } /** Requests unknown cosmetics from the connection manager if they are not already populated or loading */ @@ -209,15 +213,15 @@ class InfraCosmeticsData private constructor( activeCosmeticRequests[id] = Instant.now() } - connectionManager.send( - ClientCosmeticRequestPacket(unknownIds.toSet(), null), - { _ -> - for (id in unknownIds) { - activeCosmeticRequests.remove(id) - } - }, - TimeUnit.SECONDS, CosmeticsManager.LOAD_TIMEOUT_SECONDS, - ) + cmConnection.connectionScope.launch { + cmConnection.call(ClientCosmeticRequestPacket(unknownIds.toSet(), null)) + .exponentialBackoff() + .await() + + for (id in unknownIds) { + activeCosmeticRequests.remove(id) + } + } } /** Requests unknown bundles from the connection manager if they are not already populated or loading */ @@ -229,15 +233,15 @@ class InfraCosmeticsData private constructor( activeBundleRequests[id] = Instant.now() } - connectionManager.send( - ClientWardrobeStoreBundleRequestPacket(unknownIds.toSet()), - { _ -> - for (id in unknownIds) { - activeBundleRequests.remove(id) - } - }, - TimeUnit.SECONDS, CosmeticsManager.LOAD_TIMEOUT_SECONDS, - ) + cmConnection.connectionScope.launch { + cmConnection.call(ClientWardrobeStoreBundleRequestPacket(unknownIds.toSet())) + .exponentialBackoff() + .await() + + for (id in unknownIds) { + activeBundleRequests.remove(id) + } + } } fun hasActiveRequests(timeoutMs: Long): Boolean { diff --git a/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/cosmetics/localCosmeticBundleManagement.kt b/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/cosmetics/localCosmeticBundleManagement.kt index 3e44e76..c81cb3c 100644 --- a/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/cosmetics/localCosmeticBundleManagement.kt +++ b/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/cosmetics/localCosmeticBundleManagement.kt @@ -24,6 +24,7 @@ fun CosmeticsDataWithChanges.registerBundle( name: String, tier: CosmeticTier, discount: Float, + rotateOnPreview: Boolean, skin: CosmeticBundle.Skin, cosmetics: Map, settings: Map>, @@ -38,6 +39,7 @@ fun CosmeticsDataWithChanges.registerBundle( name, tier, discount, + rotateOnPreview, skin, cosmetics, settings diff --git a/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/cosmetics/localCosmeticManagement.kt b/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/cosmetics/localCosmeticManagement.kt index 36411b3..60a291c 100644 --- a/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/cosmetics/localCosmeticManagement.kt +++ b/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/cosmetics/localCosmeticManagement.kt @@ -81,6 +81,7 @@ fun CosmeticsDataWithChanges.setCosmeticSingletonPropertyEnabled( is CosmeticProperty.TransitionDelay -> existingProperty.copy(enabled = enabled) is CosmeticProperty.Variants -> existingProperty.copy(enabled = enabled) is CosmeticProperty.DefaultSide -> existingProperty.copy(enabled = enabled) + is CosmeticProperty.MutuallyExclusive -> existingProperty.copy(enabled = enabled) is CosmeticProperty.CosmeticBoneHiding, is CosmeticProperty.ExternalHiddenBone, @@ -147,6 +148,12 @@ fun CosmeticsDataWithChanges.setCosmeticSingletonPropertyEnabled( CosmeticProperty.DefaultSide.Data(Side.getDefaultSideOrNull(Side.values().toSet()) ?: Side.LEFT) ) + CosmeticPropertyType.MUTUALLY_EXCLUSIVE -> CosmeticProperty.MutuallyExclusive( + "UNUSED", + enabled, + CosmeticProperty.MutuallyExclusive.Data(emptySet()) + ) + CosmeticPropertyType.COSMETIC_BONE_HIDING, CosmeticPropertyType.EXTERNAL_HIDDEN_BONE -> throw IllegalArgumentException("$type is not a singleton property") diff --git a/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/skins/SkinItem.kt b/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/skins/SkinItem.kt new file mode 100644 index 0000000..5fa8f45 --- /dev/null +++ b/gui/essential/src/main/kotlin/gg/essential/network/connectionmanager/skins/SkinItem.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.network.connectionmanager.skins + +import gg.essential.cosmetics.SkinId +import gg.essential.mod.Skin +import java.time.Instant + +data class SkinItem( + val id: SkinId, + val name: String, + val skin: Skin, + val createdAt: Instant, + val lastUsedAt: Instant?, + val favoritedSince: Instant?, +) diff --git a/src/main/kotlin/gg/essential/network/cosmetics/conversions.kt b/gui/essential/src/main/kotlin/gg/essential/network/cosmetics/conversions.kt similarity index 85% rename from src/main/kotlin/gg/essential/network/cosmetics/conversions.kt rename to gui/essential/src/main/kotlin/gg/essential/network/cosmetics/conversions.kt index 791ffdb..c797edf 100644 --- a/src/main/kotlin/gg/essential/network/cosmetics/conversions.kt +++ b/gui/essential/src/main/kotlin/gg/essential/network/cosmetics/conversions.kt @@ -13,7 +13,6 @@ package gg.essential.network.cosmetics import gg.essential.cosmetics.model.CosmeticStoreBundle import gg.essential.cosmetics.model.CosmeticStoreBundleSkin -import gg.essential.gui.wardrobe.Item import gg.essential.lib.gson.Gson import gg.essential.mod.EssentialAsset import gg.essential.mod.Model @@ -23,6 +22,7 @@ import gg.essential.mod.cosmetics.settings.CosmeticProperty import gg.essential.mod.cosmetics.settings.CosmeticSetting import gg.essential.mod.cosmetics.settings.UntypedCosmeticSetting import gg.essential.network.connectionmanager.coins.CoinBundle +import gg.essential.network.connectionmanager.skins.SkinItem import gg.essential.skins.SkinModel import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json @@ -31,7 +31,6 @@ import gg.essential.coins.model.CoinBundle as InfraCoinBundle import gg.essential.cosmetics.CosmeticSlot as InfraCosmeticSlot import gg.essential.cosmetics.SkinLayer as InfraSkinLayer import gg.essential.cosmetics.model.Cosmetic as InfraCosmetic -import gg.essential.cosmetics.model.CosmeticAssets as InfraCosmeticAssets import gg.essential.cosmetics.model.CosmeticCategory as InfraCosmeticCategory import gg.essential.cosmetics.model.CosmeticOutfit as InfraCosmeticOutfit import gg.essential.cosmetics.model.CosmeticSetting as InfraCosmeticSetting @@ -46,7 +45,7 @@ import gg.essential.skins.model.Skin as InfraSkin // fun CosmeticSlot.toInfra() = InfraCosmeticSlot.of(id) -fun InfraEmoteWheel.toMod() = EmoteWheelPage(id(), createdAt().toInstant(), selected(), slots().let {slots -> +fun InfraEmoteWheel.toMod() = EmoteWheelPage(id(), createdAt().toInstant(), slots().let {slots -> mutableListOf(*arrayOfNulls(8)).also { slots.forEach { (t, u) -> it[t] = u } } @@ -72,7 +71,7 @@ fun InfraCosmeticType.toMod() = CosmeticType(id, slot.toMod(), displayNames, ski fun CosmeticStoreBundleSkin.toMod() = CosmeticBundle.Skin(Skin(hash, model.toMod()), name) -fun CosmeticStoreBundle.toMod() = CosmeticBundle(id, name, tier.toMod(), discount, skin.toMod(), cosmetics.toMod(), settings.toModSetting()) +fun CosmeticStoreBundle.toMod() = CosmeticBundle(id, name, tier.toMod(), discount, rotateOnPreview, skin.toMod(), cosmetics.toMod(), settings.toModSetting()) fun Model.toInfra() = when (this) { Model.STEVE -> SkinModel.CLASSIC @@ -84,7 +83,7 @@ fun SkinModel.toMod() = when (this) { SkinModel.SLIM -> Model.ALEX } -fun InfraSkin.toMod() = Item.SkinItem(id, name, Skin(hash, model.toMod()), createdAt.toInstant(), lastUsedAt?.toInstant(), favoritedAt?.toInstant()) +fun InfraSkin.toMod() = SkinItem(id, name, Skin(hash, model.toMod()), createdAt.toInstant(), lastUsedAt?.toInstant(), favoritedAt?.toInstant()) fun InfraCosmeticTier?.toMod() = when (this) { InfraCosmeticTier.COMMON -> CosmeticTier.COMMON @@ -110,17 +109,6 @@ fun InfraCosmeticCategory.toMod() = CosmeticCategory( fun InfraEssentialAsset.toMod() = EssentialAsset(url, checksum) -fun InfraCosmeticAssets.toMod(providedMap: Map?) = CosmeticAssets(providedMap ?: buildMap { - thumbnail.let { put("thumbnail.png", it.toMod()) } - texture?.let { put("texture.png", it.toMod()) } - geometry.steve.let { put("geometry.steve.json", it.toMod()) } - geometry.alex?.let { put("geometry.alex.json", it.toMod()) } - animations?.let { put("animations.json", it.toMod()) } - skinMask?.steve?.let { put("skin_mask.steve.png", it.toMod()) } - skinMask?.alex?.let { put("skin_mask.alex.png", it.toMod()) } - settings?.let { put("settings.json", it.toMod()) } -}) - fun InfraCosmeticOutfit.toMod() = CosmeticOutfit( id, name, @@ -138,7 +126,7 @@ fun InfraCosmetic.toMod(type: CosmeticType, settings: List): C type, tier.toMod(), displayNames, - assets?.toMod(assetsMap?.mapValues { it.value.toMod() })?.allFiles ?: emptyMap(), + assetsMap.mapValues { it.value.toMod() }, settings, storePackageId, priceCoins?.let { mapOf("coins" to it.toDouble()) } ?: mapOf(), diff --git a/gui/essential/src/main/kotlin/gg/essential/sps/IntegratedServerManager.kt b/gui/essential/src/main/kotlin/gg/essential/sps/IntegratedServerManager.kt new file mode 100644 index 0000000..77df304 --- /dev/null +++ b/gui/essential/src/main/kotlin/gg/essential/sps/IntegratedServerManager.kt @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.sps + +import gg.essential.gui.elementa.state.v2.ListState +import gg.essential.gui.elementa.state.v2.MutableState +import gg.essential.gui.elementa.state.v2.State +import kotlinx.coroutines.CoroutineScope +import java.nio.file.Path +import java.util.* + +// Note: This object is for use from the logical client side and will internally communicate with the server. +// Unless explicitly noted otherwise, despite its name, it may not be accessed from the server thread. +interface IntegratedServerManager { + val worldFolder: Path + + val serverPort: State + val thirdPartyVoicePort: State + + /** + * A coroutine scope which is cancelled when the server begins to shut down. + * Coroutines are executed using [Client], not on the server thread! + */ + val coroutineScope: CoroutineScope + + /** Current server whitelist. May be `null` if no whitelist has been applied yet. */ + val whitelist: State?> + + /** UUID of every player connected (and logged in) to this server. Includes the host while they are connected. */ + val connectedPlayers: ListState + /** [connectedPlayers] excluding the host */ + val connectedGuests: ListState + + /** + * The JSON which the server will return as its server list status when pinged. + * May be `null` if the server hasn't prepared a response yet. + */ + val statusResponseJson: State + + fun setOpenToLanSource(source: State) + fun setWhitelistSource(source: State>) + fun setOpsSource(source: State>) + fun setResourcePackSource(source: State) + fun setDifficultySource(source: MutableState) + fun setDefaultGameModeSource(source: State) + fun setCheatsEnabledSource(source: State) + + data class ServerResourcePack(val url: String, val checksum: String) + + enum class Difficulty { + Peaceful, + Easy, + Normal, + Hard, + ; + companion object + } + enum class GameMode { + Survival, + Creative, + Adventure, + Spectator, + ; + companion object + } +} diff --git a/gui/essential/src/main/kotlin/gg/essential/util/HttpUtils.kt b/gui/essential/src/main/kotlin/gg/essential/util/HttpUtils.kt index e96c0fc..39231ee 100644 --- a/gui/essential/src/main/kotlin/gg/essential/util/HttpUtils.kt +++ b/gui/essential/src/main/kotlin/gg/essential/util/HttpUtils.kt @@ -14,6 +14,7 @@ package gg.essential.util import gg.essential.data.VersionInfo import gg.essential.handlers.CertChain +import kotlinx.coroutines.future.asDeferred import okhttp3.OkHttpClient import java.util.concurrent.CompletableFuture import java.util.concurrent.TimeUnit @@ -33,3 +34,9 @@ val httpClient: CompletableFuture = CompletableFuture.supplyAsync ) }.build() } + +private val httpClientDeferred = httpClient.asDeferred() + +suspend fun httpClient(): OkHttpClient = + httpClientDeferred.await() + diff --git a/gui/essential/src/main/kotlin/gg/essential/util/SingleThreadDispatcher.kt b/gui/essential/src/main/kotlin/gg/essential/util/SingleThreadDispatcher.kt new file mode 100644 index 0000000..4a55a71 --- /dev/null +++ b/gui/essential/src/main/kotlin/gg/essential/util/SingleThreadDispatcher.kt @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.util + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.Runnable +import kotlin.coroutines.CoroutineContext + +/** + * A dispatcher which will queue all tasks to run when [runTasks] is called. + * + * After [shutdown], when [runTasks] will no longer be called, it'll instead dispatch all tasks to [Dispatchers.IO] + * with parallelism limited to 1. + */ +class SingleThreadDispatcher(private val name: String) : CoroutineDispatcher() { + /** + * Lock guarding all access to [queue] and [shutdownDelegate]. + * Note: This lock MUST NOT be held while executing task, only for polling the queue, because that would block + * other threads from submitting tasks for that entire duration. + */ + private val lock = Any() + private val queue = ArrayDeque() + private var shutdownDelegate: CoroutineDispatcher? = null + + override fun dispatch(context: CoroutineContext, block: Runnable) { + synchronized(lock) { + val shutdownDelegate = shutdownDelegate + if (shutdownDelegate == null) { + queue.addLast(block) + } else { + shutdownDelegate.dispatch(context, block) + } + } + } + + fun runTasks() { + while (true) { + val task = synchronized(lock) { + check(shutdownDelegate == null) { "Dispatcher has been shut down" } + queue.removeFirstOrNull() + } ?: break + task.run() + } + } + + @OptIn(ExperimentalCoroutinesApi::class) // FIXME becomes stable with an extra arg in 1.9 + fun shutdown() { + while (true) { + runTasks() + synchronized(lock) { + // We must only switch to the delegate if there are no more tasks queued at this point; someone could + // have queued another one between this synchronized block and the one in runTasks. + if (queue.isEmpty()) { + shutdownDelegate = Dispatchers.IO.limitedParallelism(1) + return + } + } + } + } + + override fun toString(): String { + return name + } +} diff --git a/src/main/kotlin/gg/essential/util/currency.kt b/gui/essential/src/main/kotlin/gg/essential/util/currency.kt similarity index 100% rename from src/main/kotlin/gg/essential/util/currency.kt rename to gui/essential/src/main/kotlin/gg/essential/util/currency.kt diff --git a/gui/essential/src/main/kotlin/gg/essential/util/essentialGuiExtensions.kt b/gui/essential/src/main/kotlin/gg/essential/util/essentialGuiExtensions.kt index a94a319..2e0b42f 100644 --- a/gui/essential/src/main/kotlin/gg/essential/util/essentialGuiExtensions.kt +++ b/gui/essential/src/main/kotlin/gg/essential/util/essentialGuiExtensions.kt @@ -42,6 +42,8 @@ import gg.essential.gui.util.stateBy import gg.essential.vigilance.utils.onLeftClick import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.asExecutor +import org.apache.logging.log4j.LogManager +import org.apache.logging.log4j.Logger import java.awt.Color import java.util.concurrent.CompletableFuture @@ -440,6 +442,32 @@ inline fun T.onRightClick(crossinline method: UIComponent.(eve fun CompletableFuture.thenAcceptOnMainThread(callback: (T) -> Unit): CompletableFuture = this.thenAcceptAsync({ callback(it) }, Dispatchers.Client.asExecutor()) +private val ESSENTIAL_LOGGER = LogManager.getLogger("Essential Logger") + +@JvmOverloads +fun CompletableFuture.logExceptions(logger: Logger = ESSENTIAL_LOGGER): CompletableFuture = + whenComplete { _, e -> + if (e != null) { + logger.error("Unhandled error:", e) + } + } + fun CompletableFuture.toState(): gg.essential.gui.elementa.state.v2.State = toState(Dispatchers.Client.asExecutor()) +/** + * Returns a darker shade of this color by reducing its brightness. + * + * @param percentage The fraction by which to darken the color, where: + * - `0.0f` returns the original color. + * - `1.0f` returns black. + */ +fun Color.darker(percentage: Float): Color { + val brightnessFactor = 1.0f - percentage + return Color( + (red * brightnessFactor).toInt().coerceIn(0, 255), + (green * brightnessFactor).toInt().coerceIn(0, 255), + (blue * brightnessFactor).toInt().coerceIn(0, 255), + alpha + ) +} diff --git a/gui/essential/src/main/kotlin/gg/essential/util/guiEssentialPlatform.kt b/gui/essential/src/main/kotlin/gg/essential/util/guiEssentialPlatform.kt index 428b307..aec0c8d 100644 --- a/gui/essential/src/main/kotlin/gg/essential/util/guiEssentialPlatform.kt +++ b/gui/essential/src/main/kotlin/gg/essential/util/guiEssentialPlatform.kt @@ -13,11 +13,18 @@ package gg.essential.util import gg.essential.elementa.components.Window import gg.essential.gui.common.modal.Modal +import gg.essential.gui.elementa.essentialmarkdown.EssentialMarkdown import gg.essential.gui.elementa.state.v2.MutableState import gg.essential.gui.elementa.state.v2.State +import gg.essential.gui.friends.message.v2.MessageRef +import gg.essential.gui.friends.state.SocialStates +import gg.essential.gui.notification.NotificationsManager import gg.essential.gui.overlay.ModalManager +import gg.essential.gui.overlay.OverlayManager import gg.essential.model.backend.RenderBackend import gg.essential.network.CMConnection +import gg.essential.network.connectionmanager.cosmetics.AssetLoader +import gg.essential.network.connectionmanager.notices.INoticesManager import gg.essential.universal.UImage import gg.essential.universal.utils.ReleasedDynamicTexture import gg.essential.util.image.bitmap.MutableBitmap @@ -32,9 +39,13 @@ interface GuiEssentialPlatform { val renderThreadDispatcher: CoroutineDispatcher val renderBackend: RenderBackend + val overlayManager: OverlayManager + val assetLoader: AssetLoader val cmConnection: CMConnection + val notifications: NotificationsManager + fun createModalManager(): ModalManager fun onResourceManagerReload(runnable: Runnable) @@ -55,17 +66,30 @@ interface GuiEssentialPlatform { val config: Config val pauseMenuDisplayWindow: Window + val mcProtocolVersion: Int + val mcGameVersion: String + fun currentServerType(): ServerType? fun registerActiveSessionState(state: MutableState) + fun createSocialStates(): SocialStates + + fun resolveMessageRef(messageRef: MessageRef) + + val essentialUriListener: EssentialMarkdown.(EssentialMarkdown.LinkClickEvent) -> Unit + + val noticesManager: INoticesManager + + val isOptiFineInstalled: Boolean + interface Config { val shouldDarkenRetexturedButtons: Boolean val useVanillaButtonForRetexturing: State } companion object { - internal val platform: GuiEssentialPlatform = + val platform: GuiEssentialPlatform = Class.forName(GuiEssentialPlatform::class.java.name + "Impl").newInstance() as GuiEssentialPlatform } } diff --git a/gui/essential/src/main/kotlin/gg/essential/util/openLinkInBrowser.kt b/gui/essential/src/main/kotlin/gg/essential/util/openLinkInBrowser.kt new file mode 100644 index 0000000..0421fa6 --- /dev/null +++ b/gui/essential/src/main/kotlin/gg/essential/util/openLinkInBrowser.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.util + +import gg.essential.api.gui.NotificationType +import gg.essential.api.gui.Slot +import gg.essential.gui.EssentialPalette +import gg.essential.gui.notification.Notifications +import gg.essential.gui.notification.toastButton +import gg.essential.universal.UDesktop +import java.net.URI + +fun openInBrowser(uri: URI) { + if (UDesktop.browse(uri)) { + Notifications.push("", "Link opened in browser") { + withCustomComponent(Slot.ICON, EssentialPalette.JOIN_ARROW_5X.create()) + } + } else { + Notifications.pushPersistentToast("Can't open browser", "Unable to open link in browser.", {}, {}) { + type = NotificationType.WARNING + withCustomComponent(Slot.ACTION, toastButton("Copy Link") { + UDesktop.setClipboardString(uri.toString()) + }) + } + } +} diff --git a/gui/essential/src/main/kotlin/gg/essential/util/timeFormat.kt b/gui/essential/src/main/kotlin/gg/essential/util/timeFormat.kt new file mode 100644 index 0000000..d786cb7 --- /dev/null +++ b/gui/essential/src/main/kotlin/gg/essential/util/timeFormat.kt @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.util + +import gg.essential.config.EssentialConfig +import java.time.Duration +import java.time.Instant +import java.time.LocalDate +import java.time.ZoneId +import java.time.format.DateTimeFormatter +import java.time.temporal.TemporalAccessor +import java.util.* +import java.util.concurrent.TimeUnit + +const val DATE_FORMAT = "MMM dd, yyyy" +const val DATE_FORMAT_NO_YEAR = "MMM dd" + +fun getTimeFormat(includeSeconds: Boolean): String { + val seconds = if (includeSeconds) ":ss" else "" + return if (EssentialConfig.timeFormat == 0) "h:mm$seconds a" else "HH:mm$seconds" +} + +fun formatDate(date: LocalDate, displayYear: Boolean = true) = + date.formatter(if (displayYear) DATE_FORMAT else DATE_FORMAT_NO_YEAR) + +fun formatTime(time: TemporalAccessor, includeSeconds: Boolean) = + time.formatter(getTimeFormat(includeSeconds)) + +fun formatDateAndTime(date: TemporalAccessor) = + date.formatter("$DATE_FORMAT @ ${getTimeFormat(includeSeconds = false)}") + +fun TemporalAccessor.formatter(pattern: String): String { + val format = DateTimeFormatter.ofPattern(pattern, Locale.ENGLISH).withZone(ZoneId.systemDefault()) + return format.format(this) +} + +private val weeksTimeMap = TreeMap( + mutableMapOf( + TimeUnit.DAYS.toMillis(7) to "w", + TimeUnit.DAYS.toMillis(1) to "d", + TimeUnit.HOURS.toMillis(1) to "h", + TimeUnit.MINUTES.toMillis(1) to "m", + TimeUnit.SECONDS.toMillis(1) to "s" + ) +) + +private val daysTimeMap = TreeMap( + mutableMapOf( + TimeUnit.DAYS.toMillis(1) to "d", + TimeUnit.HOURS.toMillis(1) to "h", + TimeUnit.MINUTES.toMillis(1) to "m", + TimeUnit.SECONDS.toMillis(1) to "s" + ) +) + +fun Instant.toCosmeticOptionTime(): String { + return Duration.between(Instant.now(), this).toShortString() +} + +fun Duration.toShortString(weeks: Boolean = true, expiredText: String = "Expired"): String { + val delta = toMillis() + val timeMap = if (weeks) weeksTimeMap else daysTimeMap + + val ceilEntry = timeMap.floorEntry(delta) ?: return expiredText + val floorEntry = timeMap.floorEntry(ceilEntry.key - 1) + if (ceilEntry === floorEntry || floorEntry == null) { + return (delta / ceilEntry.key).toString() + ceilEntry.value + } + val ceilUnits = delta / ceilEntry.key + val floorUnits = (delta - ceilUnits * ceilEntry.key) / floorEntry.key + + return ceilUnits.toString() + ceilEntry.value + " " + floorUnits.toString() + floorEntry.value +} diff --git a/gui/vigilance/build.gradle.kts b/gui/vigilance/build.gradle.kts index 49b2f07..8169147 100644 --- a/gui/vigilance/build.gradle.kts +++ b/gui/vigilance/build.gradle.kts @@ -19,6 +19,7 @@ plugins { dependencies { universalLibs() implementation(project(":feature-flags")) + implementation(project(":vigilance2")) implementation(project(":gui:elementa")) implementation(project(":gui:essential")) } diff --git a/gui/vigilance/src/main/kotlin/gg/essential/gui/vigilancev2/components/settings.kt b/gui/vigilance/src/main/kotlin/gg/essential/gui/vigilancev2/components/settings.kt index 582796a..962fe2a 100644 --- a/gui/vigilance/src/main/kotlin/gg/essential/gui/vigilancev2/components/settings.kt +++ b/gui/vigilance/src/main/kotlin/gg/essential/gui/vigilancev2/components/settings.kt @@ -22,8 +22,8 @@ import gg.essential.gui.common.shadow.ShadowEffect import gg.essential.gui.elementa.state.v2.mutableListStateOf import gg.essential.gui.elementa.state.v2.toV1 import gg.essential.gui.layoutdsl.* -import gg.essential.gui.vigilancev2.builder.StateBackedPropertyValue import gg.essential.gui.vigilancev2.palette.VigilancePalette +import gg.essential.mod.vigilance2.builder.StateBackedPropertyValue import gg.essential.vigilance.data.PropertyData import gg.essential.vigilance.data.PropertyType import gg.essential.vigilance.gui.settings.* diff --git a/gui/vigilance/src/main/kotlin/gg/essential/gui/vigilancev2/data.kt b/gui/vigilance/src/main/kotlin/gg/essential/gui/vigilancev2/data.kt index 4b843da..6bcdf4d 100644 --- a/gui/vigilance/src/main/kotlin/gg/essential/gui/vigilancev2/data.kt +++ b/gui/vigilance/src/main/kotlin/gg/essential/gui/vigilancev2/data.kt @@ -14,8 +14,8 @@ package gg.essential.gui.vigilancev2 import gg.essential.gui.elementa.state.v2.State import gg.essential.gui.elementa.state.v2.stateBy import gg.essential.gui.elementa.state.v2.toListState -import gg.essential.gui.vigilancev2.builder.VisibleDependencyPredicate import gg.essential.gui.vigilancev2.utils.containsSearchTerm +import gg.essential.mod.vigilance2.builder.VisibleDependencyPredicate import gg.essential.vigilance.data.PropertyData class Category(val name: String, val subcategories: List) diff --git a/settings.gradle.kts b/settings.gradle.kts index c7babe0..f30fe9e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -64,6 +64,7 @@ val subprojects = listOf( ":quic-connector", ":slf4j-to-log4j", ":utils", + ":vigilance2", ) for (fullName in subprojects) { @@ -100,6 +101,7 @@ listOf( "1.20.4-forge", "1.20.6-fabric", "1.21-fabric", + "1.21.2-fabric", ).forEach { version -> include(":$version") project(":$version").apply { diff --git a/src/main/java/gg/essential/Essential.java b/src/main/java/gg/essential/Essential.java index cc5de3d..1492fe1 100644 --- a/src/main/java/gg/essential/Essential.java +++ b/src/main/java/gg/essential/Essential.java @@ -19,6 +19,7 @@ import gg.essential.config.AccessedViaReflection; import gg.essential.config.EssentialConfig; import gg.essential.config.EssentialConfigApiImpl; +import gg.essential.config.McEssentialConfig; import gg.essential.cosmetics.PlayerWearableManager; import gg.essential.cosmetics.events.AnimationEffectHandler; import gg.essential.data.OnboardingData; @@ -35,6 +36,8 @@ import gg.essential.gui.account.factory.*; import gg.essential.gui.api.ComponentFactory; import gg.essential.gui.common.UI3DPlayer; +import gg.essential.gui.elementa.state.v2.MutableState; +import gg.essential.gui.image.ResourceImageFactory; import gg.essential.gui.notification.Notifications; import gg.essential.handlers.OptionsScreenOverlay; import gg.essential.gui.overlay.OverlayManager; @@ -46,6 +49,7 @@ import gg.essential.lib.gson.Gson; import gg.essential.lib.gson.GsonBuilder; import gg.essential.network.connectionmanager.ConnectionManager; +import gg.essential.sps.McIntegratedServerManager; import gg.essential.universal.UMinecraft; import gg.essential.util.*; import gg.essential.util.crash.StacktraceDeobfuscator; @@ -78,6 +82,8 @@ import net.minecraftforge.fml.common.ModContainer; //#endif +import static gg.essential.gui.elementa.state.v2.StateKt.mutableStateOf; + public class Essential implements EssentialAPI { public static final String MODID = "essential"; public static final String NAME = "Essential"; @@ -122,8 +128,9 @@ public class Essential implements EssentialAPI { public final boolean isNewInstallation = !new File(baseDir, "config.toml").exists(); private final Lwjgl3Loader lwjgl3 = new Lwjgl3Loader(baseDir.toPath().resolve("lwjgl3-natives"), GLUtil.INSTANCE.isGL30()); + private final MutableState<@Nullable McIntegratedServerManager> integratedServerManager = mutableStateOf(null); @NotNull - private final ConnectionManager connectionManager = new ConnectionManager(new NetworkHook(), baseDir, lwjgl3); + private final ConnectionManager connectionManager = new ConnectionManager(new NetworkHook(), baseDir, lwjgl3, integratedServerManager); private final List sessionFactories = new ArrayList<>(); @NotNull private final EssentialKeybindingRegistry keybindingRegistry = new EssentialKeybindingRegistry(); @@ -161,6 +168,11 @@ public static Essential getInstance() { } } + @NotNull + public MutableState<@Nullable McIntegratedServerManager> getIntegratedServerManager() { + return this.integratedServerManager; + } + @NotNull public ConnectionManager getConnectionManager() { return this.connectionManager; @@ -201,7 +213,10 @@ private void dispatchStaticInitializers() { Multithreading.runAsync(() -> ElementaFonts.INSTANCE.getClass()); Multithreading.runAsync(() -> EssentialAPI.Companion.getClass()); Multithreading.runAsync(() -> AutoUpdate.INSTANCE.getClass()); - Multithreading.runAsync(() -> EssentialPalette.INSTANCE.getClass()); + Multithreading.runAsync(() -> { + EssentialPalette.INSTANCE.getClass(); + ResourceImageFactory.Companion.preload(); + }); } @SuppressWarnings({ @@ -267,7 +282,7 @@ private void init() { EventHandler.init(); StencilEffect.Companion.enableStencil(); - essentialConfig.hookUp(); + McEssentialConfig.INSTANCE.hookUp(); //#if MC<11400 createStacktraceDeobfuscator(); //#endif @@ -348,6 +363,7 @@ private void init() { } EssentialChannelHandler.registerEssentialChannel(); + } private File createEssentialDir() { diff --git a/src/main/java/gg/essential/commands/impl/CommandConfig.java b/src/main/java/gg/essential/commands/impl/CommandConfig.java index c850cbd..a6db3aa 100644 --- a/src/main/java/gg/essential/commands/impl/CommandConfig.java +++ b/src/main/java/gg/essential/commands/impl/CommandConfig.java @@ -13,7 +13,7 @@ import gg.essential.api.commands.Command; import gg.essential.api.commands.DefaultHandler; -import gg.essential.config.EssentialConfig; +import gg.essential.config.McEssentialConfig; import gg.essential.gui.vigilancev2.VigilanceV2SettingsGui; import gg.essential.util.GuiUtil; @@ -27,6 +27,6 @@ public CommandConfig() { @DefaultHandler public void handle() { - GuiUtil.openScreen(VigilanceV2SettingsGui.class, EssentialConfig.INSTANCE::gui); + GuiUtil.openScreen(VigilanceV2SettingsGui.class, McEssentialConfig.INSTANCE::gui); } } diff --git a/src/main/java/gg/essential/cosmetics/EssentialModelRenderer.java b/src/main/java/gg/essential/cosmetics/EssentialModelRenderer.java index f756aaf..3b40989 100644 --- a/src/main/java/gg/essential/cosmetics/EssentialModelRenderer.java +++ b/src/main/java/gg/essential/cosmetics/EssentialModelRenderer.java @@ -46,6 +46,11 @@ import static gg.essential.gui.elementa.state.v2.StateKt.stateOf; import static gg.essential.util.ExtensionsKt.toCommon; +//#if MC>=12102 +//$$ import gg.essential.mixins.impl.client.model.PlayerEntityRenderStateExt; +//$$ import net.minecraft.client.render.entity.state.PlayerEntityRenderState; +//#endif + //#if MC>=11400 //$$ import com.mojang.blaze3d.matrix.MatrixStack; //$$ import net.minecraft.client.renderer.IRenderTypeBuffer; @@ -56,7 +61,9 @@ import static gg.essential.model.backend.minecraft.LegacyCameraPositioningKt.getRelativeCameraPosFromGlState; //#endif -//#if MC>=11400 +//#if MC>=12102 +//$$ public class EssentialModelRenderer extends FeatureRenderer { +//#elseif MC>=11400 //$$ public class EssentialModelRenderer extends LayerRenderer> { //#else public class EssentialModelRenderer implements LayerRenderer { @@ -227,7 +234,12 @@ public void render( @Override //#if MC>=11400 + //#if MC>=12102 + //$$ public void render(MatrixStack vMatrixStack, VertexConsumerProvider buffer, int light, PlayerEntityRenderState state, float limbAngle, float limbDistance) { + //$$ AbstractClientPlayerEntity player = ((PlayerEntityRenderStateExt) state).essential$getEntity(); + //#else //$$ public void render(@NotNull MatrixStack vMatrixStack, @NotNull IRenderTypeBuffer buffer, int light, @NotNull AbstractClientPlayerEntity player, float limbSwing, float limbSwingAmount, float partialTicks, float ageInTicks, float netHeadYaw, float headPitch) { + //#endif //$$ UMatrixStack matrixStack = new UMatrixStack(vMatrixStack); //$$ RenderBackend.VertexConsumerProvider vertexConsumerProvider = new MinecraftRenderBackend.VertexConsumerProvider(buffer, light); //#else diff --git a/src/main/java/gg/essential/cosmetics/PlayerWearableManager.java b/src/main/java/gg/essential/cosmetics/PlayerWearableManager.java index ad4b7a3..100d438 100644 --- a/src/main/java/gg/essential/cosmetics/PlayerWearableManager.java +++ b/src/main/java/gg/essential/cosmetics/PlayerWearableManager.java @@ -14,11 +14,11 @@ import com.google.common.collect.ImmutableMap; import com.mojang.authlib.minecraft.MinecraftProfileTexture; import gg.essential.api.cosmetics.RenderCosmetic; -import gg.essential.config.EssentialConfig; import gg.essential.cosmetics.source.CosmeticsSource; import gg.essential.event.entity.PlayerTickEvent; import gg.essential.gui.common.EmulatedUI3DPlayer; import gg.essential.mixins.impl.client.entity.AbstractClientPlayerExt; +import gg.essential.mixins.impl.client.renderer.entity.ArmorRenderingUtil; import gg.essential.mod.Model; import gg.essential.mod.cosmetics.CosmeticSlot; import gg.essential.mod.cosmetics.CosmeticType; @@ -190,7 +190,7 @@ private Set getArmourFromPlayer(AbstractClientPlayer player) { Set equippedSlots = new HashSet<>(); - int armorSetting = EssentialConfig.INSTANCE.getCosmeticArmorSetting(player); + int armorSetting = ArmorRenderingUtil.getCosmeticArmorSetting(player); if (armorSetting > 0) { return equippedSlots; } @@ -219,10 +219,16 @@ private boolean canRenderCosmetic(AbstractClientPlayer player, int slot) { //#endif ItemStack stack = inventory.armorItemInSlot(slot); + if (isEmpty(stack)) return true; + if (stack.getItem() instanceof RenderCosmetic) return true; + //#if MC>=12102 + //$$ if (stack.getItem() == net.minecraft.item.Items.ELYTRA) return true; + //#endif final boolean[] armorRenderingSuppressed = ((AbstractClientPlayerExt) player).wasArmorRenderingSuppressed(); + if (armorRenderingSuppressed[slot]) return true; - return isEmpty(stack) || stack.getItem() instanceof RenderCosmetic || armorRenderingSuppressed[slot]; + return false; } private boolean isEmpty(ItemStack stack) { diff --git a/src/main/java/gg/essential/event/render/RenderTickEvent.java b/src/main/java/gg/essential/event/render/RenderTickEvent.java index 50d734b..03c2264 100644 --- a/src/main/java/gg/essential/event/render/RenderTickEvent.java +++ b/src/main/java/gg/essential/event/render/RenderTickEvent.java @@ -16,13 +16,15 @@ public final class RenderTickEvent { private final boolean pre; + private final boolean loadingScreen; private final UMatrixStack matrixStack; private final float partialTicksMenu; private final float partialTicksInGame; - public RenderTickEvent(boolean pre, UMatrixStack matrixStack, float partialTicksMenu, float partialTicksInGame) { + public RenderTickEvent(boolean pre, boolean loadingScreen, UMatrixStack matrixStack, float partialTicksMenu, float partialTicksInGame) { this.pre = pre; + this.loadingScreen = loadingScreen; this.matrixStack = matrixStack; this.partialTicksMenu = partialTicksMenu; this.partialTicksInGame = partialTicksInGame; @@ -32,6 +34,10 @@ public boolean isPre() { return pre; } + public boolean isLoadingScreen() { + return loadingScreen; + } + public UMatrixStack getMatrixStack() { return matrixStack; } diff --git a/src/main/java/gg/essential/key/EssentialKeybindingRegistry.java b/src/main/java/gg/essential/key/EssentialKeybindingRegistry.java index c723a3b..62a582b 100644 --- a/src/main/java/gg/essential/key/EssentialKeybindingRegistry.java +++ b/src/main/java/gg/essential/key/EssentialKeybindingRegistry.java @@ -27,6 +27,7 @@ import gg.essential.gui.wardrobe.Wardrobe; import gg.essential.handlers.PauseMenuDisplay; import gg.essential.handlers.ZoomHandler; +import gg.essential.network.connectionmanager.ConnectionManager; import gg.essential.network.connectionmanager.cosmetics.AssetLoader; import gg.essential.network.connectionmanager.cosmetics.CosmeticsManager; import gg.essential.network.connectionmanager.sps.SPSSessionSource; @@ -156,10 +157,10 @@ public KeyBinding[] registerKeyBinds(KeyBinding[] allBindings) { int index = i; new EssentialKeybinding("EMOTE_SLOT_" + (i + 1), CATEGORY, UKeyboard.KEY_NONE).requiresEssentialFull() .withInitialPress(() -> { - Essential essential = Essential.getInstance(); - CosmeticsManager cosmeticsManager = essential.getConnectionManager().getCosmeticsManager(); + ConnectionManager connectionManager = Essential.getInstance().getConnectionManager(); + CosmeticsManager cosmeticsManager = connectionManager.getCosmeticsManager(); - String emote = cosmeticsManager.getSavedEmotes().get(index); + String emote = connectionManager.getEmoteWheelManager().getSelectedEmoteWheelSlots().getUntracked().get(index); if (emote == null) { return; } diff --git a/src/main/java/gg/essential/main/Bootstrap.java b/src/main/java/gg/essential/main/Bootstrap.java index 087b763..6f2ccf4 100644 --- a/src/main/java/gg/essential/main/Bootstrap.java +++ b/src/main/java/gg/essential/main/Bootstrap.java @@ -12,6 +12,7 @@ package gg.essential.main; import gg.essential.mixins.MixinErrorHandler; +import gg.essential.mixins.IntegrationTestsPlugin; import gg.essential.util.MixinUtils; import org.apache.logging.log4j.LogManager; import org.spongepowered.asm.launch.MixinBootstrap; @@ -89,6 +90,10 @@ public static void initialize() { LogManager.getLogger().warn(Bootstrap.class.getProtectionDomain()); } //#endif + + if (IntegrationTestsPlugin.ENABLED) { + Mixins.addConfiguration("mixins.essential.tests.json"); + } } //#if MC<11400 diff --git a/src/main/java/gg/essential/mixins/IntegrationTestsPlugin.java b/src/main/java/gg/essential/mixins/IntegrationTestsPlugin.java new file mode 100644 index 0000000..cd33919 --- /dev/null +++ b/src/main/java/gg/essential/mixins/IntegrationTestsPlugin.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.mixins; + +import org.objectweb.asm.tree.ClassNode; +import org.spongepowered.asm.mixin.extensibility.IMixinConfig; +import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; +import org.spongepowered.asm.mixin.extensibility.IMixinInfo; + +import java.lang.reflect.Field; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Set; + +import static gg.essential.mixins.Plugin.hasClass; + +public class IntegrationTestsPlugin implements IMixinConfigPlugin { + public static final boolean ENABLED = System.getProperty("essential.integrationTest") != null; + + @Override + public void onLoad(String mixinPackage) { + } + + @Override + public String getRefMapperConfig() { + return null; + } + + @Override + public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { + if (!ENABLED) { + return false; + } + + if (mixinClassName.endsWith("_Emojiful")) { + if (!hasClass(targetClassName)) { + return false; + } + } + + return true; + } + + @Override + public void acceptTargets(Set myTargets, Set otherTargets) { + + } + + @Override + public List getMixins() { + return null; + } + + @Override + public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { + enableInjectionCounting(mixinInfo); + } + + @Override + public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { + } + + private static final Set injectionCountingEnabled = Collections.newSetFromMap(new IdentityHashMap<>()); + + public static void enableInjectionCounting(IMixinInfo mixinInfo) { + if (!ENABLED) { + return; + } + + IMixinConfig config = mixinInfo.getConfig(); + if (!injectionCountingEnabled.add(config)) { + return; + } + try { + Field injectorOptionsField = config.getClass().getDeclaredField("injectorOptions"); + injectorOptionsField.setAccessible(true); + Object injectorOptions = injectorOptionsField.get(config); + Field defaultRequireValueField = injectorOptions.getClass().getDeclaredField("defaultRequireValue"); + defaultRequireValueField.setAccessible(true); + defaultRequireValueField.set(injectorOptions, 1); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/gg/essential/mixins/MixinErrorHandler.java b/src/main/java/gg/essential/mixins/MixinErrorHandler.java index 464feba..5f1f94b 100644 --- a/src/main/java/gg/essential/mixins/MixinErrorHandler.java +++ b/src/main/java/gg/essential/mixins/MixinErrorHandler.java @@ -25,9 +25,16 @@ public ErrorAction onPrepareError(IMixinConfig config, Throwable th, IMixinInfo @Override public ErrorAction onApplyError(String targetClassName, Throwable th, IMixinInfo mixin, ErrorAction defaultAction) { - if (MIXIN_CONFIG_NAME.equals(mixin.getConfig().getName()) && defaultAction == ErrorAction.ERROR) { + if (MIXIN_CONFIG_NAME.equals(mixin.getConfig().getName())) { + // During integration testing, we do want to know about everything + if (IntegrationTestsPlugin.ENABLED) { + return ErrorAction.ERROR; + } + // We don't currently want to crash the game if any of our mixins fail, but we'll still log the error. - return ErrorAction.WARN; + if (defaultAction == ErrorAction.ERROR) { + return ErrorAction.WARN; + } } return defaultAction; diff --git a/src/main/java/gg/essential/mixins/Plugin.java b/src/main/java/gg/essential/mixins/Plugin.java index cd7259d..64201f5 100644 --- a/src/main/java/gg/essential/mixins/Plugin.java +++ b/src/main/java/gg/essential/mixins/Plugin.java @@ -177,6 +177,8 @@ public List getMixins() { @Override public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { + IntegrationTestsPlugin.enableInjectionCounting(mixinInfo); + for (EssentialTransformer transformer : transformerMap.get(targetClassName)) { transformer.transform(targetClass); } @@ -219,7 +221,7 @@ public void postApply(String targetClassName, ClassNode targetClass, String mixi } - private static boolean hasClass(String name) { + static boolean hasClass(String name) { try { MixinService.getService().getBytecodeProvider().getClassNode(name); return true; diff --git a/src/main/java/gg/essential/mixins/impl/client/MinecraftHook.java b/src/main/java/gg/essential/mixins/impl/client/MinecraftHook.java index 2c8e6a9..5158dc1 100644 --- a/src/main/java/gg/essential/mixins/impl/client/MinecraftHook.java +++ b/src/main/java/gg/essential/mixins/impl/client/MinecraftHook.java @@ -49,7 +49,9 @@ public void postInit() { } public void runTick() { - //#if MC < 11400 + //#if MC>=12102 + //$$ final Profiler mcProfiler = net.minecraft.util.profiler.Profilers.get(); + //#elseif MC < 11400 final Profiler mcProfiler = UMinecraft.getMinecraft().mcProfiler; //#else //$$ final IProfiler mcProfiler = UMinecraft.getMinecraft().getProfiler(); diff --git a/src/main/java/gg/essential/mixins/impl/client/model/ModelBipedUtil.java b/src/main/java/gg/essential/mixins/impl/client/model/ModelBipedUtil.java index a12cc91..161dfd1 100644 --- a/src/main/java/gg/essential/mixins/impl/client/model/ModelBipedUtil.java +++ b/src/main/java/gg/essential/mixins/impl/client/model/ModelBipedUtil.java @@ -19,7 +19,6 @@ import gg.essential.model.backend.PlayerPose; import gg.essential.model.backend.minecraft.PlayerPoseKt; import gg.essential.model.util.PlayerPoseManager; -import gg.essential.util.UUIDUtil; import net.minecraft.client.model.ModelBiped; import net.minecraft.entity.Entity; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @@ -37,9 +36,13 @@ public static void resetPose(ModelBiped model) { if (ext.getResetPose() == null) { ext.setResetPose(PlayerPoseKt.toPose(model)); } else { + //#if MC>=12102 + //$$ PlayerPoseKt.applyTo(ext.getResetPose(), model); + //#else boolean isChild = model.isChild; // child isn't set by the method we inject into, so we need to preserve it PlayerPoseKt.applyTo(ext.getResetPose(), model); model.isChild = isChild; + //#endif } } @@ -56,22 +59,6 @@ public static void applyPoseTransform(ModelBiped model, Entity entity) { WearablesManager wearablesManager = playerExt.getWearablesManager(); PlayerPoseManager poseManager = playerExt.getPoseManager(); - if (entity.getUniqueID().equals(UUIDUtil.getClientUUID())) { - if ( - //#if MC==11202 - model.leftArmPose == ModelBiped.ArmPose.BOW_AND_ARROW || model.rightArmPose == ModelBiped.ArmPose.BOW_AND_ARROW || - model.leftArmPose == ModelBiped.ArmPose.BLOCK || model.rightArmPose == ModelBiped.ArmPose.BLOCK - //#elseif MC==10809 - //$$ model.heldItemRight == 3 || model.heldItemLeft > 0 || model.aimedBow - //#else - //$$ model.leftArmPose == BipedModel.ArmPose.BOW_AND_ARROW || model.rightArmPose == BipedModel.ArmPose.BOW_AND_ARROW || - //$$ model.leftArmPose == BipedModel.ArmPose.THROW_SPEAR || model.rightArmPose == BipedModel.ArmPose.THROW_SPEAR || - //$$ model.leftArmPose == BipedModel.ArmPose.CROSSBOW_CHARGE || model.rightArmPose == BipedModel.ArmPose.CROSSBOW_CHARGE - //#endif - ) { - EmoteWheel.unequipCurrentEmote(); - } - } poseManager.update(wearablesManager); diff --git a/src/main/java/gg/essential/mixins/impl/client/model/PlayerEntityRenderStateExt.java b/src/main/java/gg/essential/mixins/impl/client/model/PlayerEntityRenderStateExt.java new file mode 100644 index 0000000..a8b2066 --- /dev/null +++ b/src/main/java/gg/essential/mixins/impl/client/model/PlayerEntityRenderStateExt.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.mixins.impl.client.model; + +import net.minecraft.client.entity.AbstractClientPlayer; + +public interface PlayerEntityRenderStateExt { + // Workaround until we've properly migrated everything to the new system + AbstractClientPlayer essential$getEntity(); + void essential$setEntity(AbstractClientPlayer entity); + + float essential$getTickDelta(); + void essential$setTickDelta(float tickDelta); +} diff --git a/src/main/java/gg/essential/mixins/impl/client/renderer/entity/ArmorRenderingUtil.java b/src/main/java/gg/essential/mixins/impl/client/renderer/entity/ArmorRenderingUtil.java index f7b35e5..3430f70 100644 --- a/src/main/java/gg/essential/mixins/impl/client/renderer/entity/ArmorRenderingUtil.java +++ b/src/main/java/gg/essential/mixins/impl/client/renderer/entity/ArmorRenderingUtil.java @@ -14,6 +14,8 @@ import gg.essential.config.EssentialConfig; import gg.essential.cosmetics.EssentialModelRenderer; import gg.essential.mixins.impl.client.entity.AbstractClientPlayerExt; +import net.minecraft.client.entity.EntityPlayerSP; +import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; public class ArmorRenderingUtil { @@ -27,10 +29,18 @@ public static boolean shouldDisableArmor(EntityLivingBase entity, int slotIndex) if (entity instanceof AbstractClientPlayerExt) { AbstractClientPlayerExt playerExt = (AbstractClientPlayerExt) entity; - int armorHidingSetting = EssentialConfig.INSTANCE.getCosmeticArmorSetting(entity); + int armorHidingSetting = getCosmeticArmorSetting(entity); return armorHidingSetting == 1 && playerExt.getCosmeticsState().getPartsEquipped().contains(slotIndex) && !EssentialModelRenderer.suppressCosmeticRendering; } return false; } + + public static int getCosmeticArmorSetting(Entity entity) { + if (entity instanceof EntityPlayerSP) { + return EssentialConfig.INSTANCE.getCosmeticArmorSettingSelf(); + } else { + return EssentialConfig.INSTANCE.getCosmeticArmorSettingOther(); + } + } } diff --git a/src/main/java/gg/essential/mixins/transformers/client/settings/Mixin_UnbindConflictingKeybinds.java b/src/main/java/gg/essential/mixins/impl/client/settings/KeybindUtils.java similarity index 51% rename from src/main/java/gg/essential/mixins/transformers/client/settings/Mixin_UnbindConflictingKeybinds.java rename to src/main/java/gg/essential/mixins/impl/client/settings/KeybindUtils.java index 8c77307..a076006 100644 --- a/src/main/java/gg/essential/mixins/transformers/client/settings/Mixin_UnbindConflictingKeybinds.java +++ b/src/main/java/gg/essential/mixins/impl/client/settings/KeybindUtils.java @@ -9,19 +9,15 @@ * commercialize, or otherwise exploit, or create derivative works based * upon, this file or any other in this repository, all of which is reserved by Essential. */ -package gg.essential.mixins.transformers.client.settings; +package gg.essential.mixins.impl.client.settings; import gg.essential.Essential; import gg.essential.key.EssentialKeybinding; import gg.essential.universal.UKeyboard; +import net.minecraft.client.Minecraft; import net.minecraft.client.settings.GameSettings; import net.minecraft.client.settings.KeyBinding; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; 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 >= 11600 //#if FORGE @@ -30,41 +26,24 @@ //$$ import net.minecraft.client.util.InputMappings; //#endif -@Mixin(GameSettings.class) -public class Mixin_UnbindConflictingKeybinds { - - @Shadow - private KeyBinding keyBindSaveToolbar; - - @Inject(method = "loadOptions", at = @At("RETURN")) - private void Essential$unbindLoadedConflictingKeybinds(CallbackInfo ci) { - // Unbind default save toolbar activator keybind if it conflicts with zoom keybind - unbindIfConflicting(getKeyBindZoom(), this.keyBindSaveToolbar); - } - - //#if MC >= 11600 - //$$ @Inject(method = "setKeyBindingCode", at = @At("RETURN")) - //$$ private void Essential$unbindSetConflictingKeybinds(KeyBinding key, InputMappings.Input keyCode, CallbackInfo ci) { - //#else - @Inject(method = "setOptionKeyBinding", at = @At("RETURN")) - private void Essential$unbindSetConflictingKeybinds(KeyBinding key, int keyCode, CallbackInfo ci) { - //#endif - // Unbind either save toolbar activator or zoom keybind if they conflict, whichever is unchanged - if (key == getKeyBindZoom()) { - unbindIfConflicting(key, this.keyBindSaveToolbar); - } else if (key == this.keyBindSaveToolbar) { - unbindIfConflicting(key, getKeyBindZoom()); - } +public class KeybindUtils { + //#if MC>=11200 + @Unique + public static KeyBinding getKeyBindSaveToolbar() { + GameSettings gameSettings = Minecraft.getMinecraft().gameSettings; + if (gameSettings == null) return null; + return gameSettings.keyBindSaveToolbar; } + //#endif @Unique - private KeyBinding getKeyBindZoom() { + public static KeyBinding getKeyBindZoom() { EssentialKeybinding zoomKey = Essential.getInstance().getKeybindingRegistry().getZoom(); return zoomKey == null ? null : zoomKey.keyBinding; } @Unique - private boolean conflicts(KeyBinding key1, KeyBinding key2) { + public static boolean conflicts(KeyBinding key1, KeyBinding key2) { //#if MC >= 11200 return key1.conflicts(key2); //#else @@ -73,7 +52,7 @@ private boolean conflicts(KeyBinding key1, KeyBinding key2) { } @Unique - private void unbindKeybind(KeyBinding keyBinding) { + public static void unbindKeybind(KeyBinding keyBinding) { //#if MC >= 11600 //#if FORGE //$$ keyBinding.setKeyModifierAndCode(KeyModifier.NONE, @@ -89,7 +68,7 @@ private void unbindKeybind(KeyBinding keyBinding) { } @Unique - private void unbindIfConflicting(KeyBinding keep, KeyBinding unbind) { + public static void unbindIfConflicting(KeyBinding keep, KeyBinding unbind) { if (keep != null & unbind != null && conflicts(keep, unbind)) { unbindKeybind(unbind); } diff --git a/src/main/java/gg/essential/mixins/transformers/client/MixinMinecraft.java b/src/main/java/gg/essential/mixins/transformers/client/MixinMinecraft.java index 6bdfc8f..747bb25 100644 --- a/src/main/java/gg/essential/mixins/transformers/client/MixinMinecraft.java +++ b/src/main/java/gg/essential/mixins/transformers/client/MixinMinecraft.java @@ -19,6 +19,9 @@ import gg.essential.Essential; import gg.essential.event.client.ReAuthEvent; import gg.essential.event.gui.GuiOpenedEvent; +import gg.essential.mixins.ext.server.integrated.IntegratedServerExt; +import gg.essential.sps.McIntegratedServerManager; +import net.minecraft.server.integrated.IntegratedServer; import net.minecraft.util.Session; import gg.essential.event.gui.GuiOpenEvent; import gg.essential.mixins.impl.client.MinecraftExt; @@ -26,6 +29,7 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiScreen; import net.minecraft.client.multiplayer.WorldClient; +import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Opcodes; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; @@ -101,15 +105,6 @@ public abstract class MixinMinecraft implements MinecraftExt { //#endif //#endif - //#if MC < 11400 - @ModifyConstant(method = "getLimitFramerate", constant = @Constant(intValue = 30)) - //#else - //$$ @ModifyConstant(method = "getFramerateLimit", constant = @Constant(intValue = 60)) - //#endif - public int modify(int value) { - return 144; - } - /** * Invoked once the game is launching */ @@ -292,4 +287,46 @@ private long mouseScroll(long ret) { } //#endif + @Shadow @Nullable private IntegratedServer integratedServer; + + //#if MC>=11900 + //$$ private static final String LAUNCH_INTEGRATED_SERVER = "startIntegratedServer"; + //#elseif MC>=11802 + //$$ private static final String LAUNCH_INTEGRATED_SERVER = "startIntegratedServer(Ljava/lang/String;Ljava/util/function/Function;Ljava/util/function/Function;ZLnet/minecraft/client/MinecraftClient$WorldLoadAction;)V"; + //#elseif MC>=11600 + //$$ private static final String LAUNCH_INTEGRATED_SERVER = "loadWorld(Ljava/lang/String;Lnet/minecraft/util/registry/DynamicRegistries$Impl;Ljava/util/function/Function;Lcom/mojang/datafixers/util/Function4;ZLnet/minecraft/client/Minecraft$WorldSelectionType;)V"; + //#else + private static final String LAUNCH_INTEGRATED_SERVER = "launchIntegratedServer"; + //#endif + //#if MC>=12005 + //$$ private static final String STOP_INTEGRATED_SERVER = "disconnect(Lnet/minecraft/client/gui/screen/Screen;Z)V"; + //#elseif MC>=11600 + //$$ private static final String STOP_INTEGRATED_SERVER = "unloadWorld(Lnet/minecraft/client/gui/screen/Screen;)V"; + //#else + private static final String STOP_INTEGRATED_SERVER = "loadWorld(Lnet/minecraft/client/multiplayer/WorldClient;Ljava/lang/String;)V"; + //#endif + + @Inject(method = { + LAUNCH_INTEGRATED_SERVER, + //$$ // Forge patches on these versions add `boolean creating` to end of arguments + //#if FORGE && MC>=11600 && MC<11900 + //#if MC>=11802 + //$$ "doLoadLevel(Ljava/lang/String;Ljava/util/function/Function;Ljava/util/function/Function;ZLnet/minecraft/client/Minecraft$ExperimentalDialogType;Z)V", + //#elseif MC>=11700 + //$$ "doLoadLevel(Ljava/lang/String;Lnet/minecraft/core/RegistryAccess$RegistryHolder;Ljava/util/function/Function;Lcom/mojang/datafixers/util/Function4;ZLnet/minecraft/client/Minecraft$ExperimentalDialogType;Z)V", + //#else + //$$ "loadWorld(Ljava/lang/String;Lnet/minecraft/util/registry/DynamicRegistries$Impl;Ljava/util/function/Function;Lcom/mojang/datafixers/util/Function4;ZLnet/minecraft/client/Minecraft$WorldSelectionType;Z)V", + //#endif + //#endif + }, at = @At(value = "FIELD", target = "Lnet/minecraft/client/Minecraft;integratedServer:Lnet/minecraft/server/integrated/IntegratedServer;", shift = At.Shift.AFTER)) + private void setIntegratedServerManager(CallbackInfo ci) { + IntegratedServerExt ext = (IntegratedServerExt) this.integratedServer; + assert ext != null; + Essential.getInstance().getIntegratedServerManager().set(ext.getEssential$manager()); + } + + @Inject(method = STOP_INTEGRATED_SERVER, at = @At(value = "FIELD", target = "Lnet/minecraft/client/Minecraft;integratedServer:Lnet/minecraft/server/integrated/IntegratedServer;", shift = At.Shift.AFTER)) + private void unsetIntegratedServerManager(CallbackInfo ci) { + Essential.getInstance().getIntegratedServerManager().set((McIntegratedServerManager) null); + } } diff --git a/src/main/java/gg/essential/mixins/transformers/client/Mixin_IncreaseMenuFpsLimit.java b/src/main/java/gg/essential/mixins/transformers/client/Mixin_IncreaseMenuFpsLimit.java new file mode 100644 index 0000000..35f9cc9 --- /dev/null +++ b/src/main/java/gg/essential/mixins/transformers/client/Mixin_IncreaseMenuFpsLimit.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.mixins.transformers.client; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.Constant; +import org.spongepowered.asm.mixin.injection.ModifyConstant; + +//#if MC>=12102 +//$$ @Mixin(net.minecraft.client.option.InactivityFpsLimiter.class) +//#else +@Mixin(net.minecraft.client.Minecraft.class) +//#endif +public abstract class Mixin_IncreaseMenuFpsLimit { + //#if MC>=12102 + //$$ @ModifyConstant(method = "update", constant = @Constant(intValue = 60)) + //#elseif MC>=11400 + //$$ @ModifyConstant(method = "getFramerateLimit", constant = @Constant(intValue = 60)) + //#else + @ModifyConstant(method = "getLimitFramerate", constant = @Constant(intValue = 30)) + //#endif + public int modify(int value) { + return 144; + } +} diff --git a/src/main/java/gg/essential/mixins/transformers/client/gui/MixinGuiMultiplayer.java b/src/main/java/gg/essential/mixins/transformers/client/gui/MixinGuiMultiplayer.java index 11ba951..b416b1a 100644 --- a/src/main/java/gg/essential/mixins/transformers/client/gui/MixinGuiMultiplayer.java +++ b/src/main/java/gg/essential/mixins/transformers/client/gui/MixinGuiMultiplayer.java @@ -11,6 +11,7 @@ */ package gg.essential.mixins.transformers.client.gui; +import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; import gg.essential.config.EssentialConfig; import gg.essential.gui.multiplayer.EssentialMultiplayerGui; import gg.essential.mixins.ext.client.gui.GuiMultiplayerExt; @@ -20,6 +21,7 @@ import net.minecraft.client.gui.GuiButton; import net.minecraft.client.gui.GuiMultiplayer; import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.gui.ServerSelectionList; import net.minecraft.client.multiplayer.ServerData; import net.minecraft.client.network.LanServerInfo; import net.minecraft.client.renderer.GlStateManager; @@ -33,7 +35,6 @@ import org.spongepowered.asm.mixin.injection.ModifyArg; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import java.util.Collections; import java.util.List; //#if MC>=12000 @@ -171,13 +172,10 @@ private GuiButton removeButton(GuiButton button) { } //#endif - @ModifyArg(method = "updateScreen", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/ServerSelectionList;updateNetworkServers(Ljava/util/List;)V")) - private List suppressLanServersOnEssentialTabs(List lanServers) { - if (!EssentialConfig.INSTANCE.getEssentialFull()) return lanServers; - if (EssentialConfig.INSTANCE.getCurrentMultiplayerTab() != 0) { - return Collections.emptyList(); - } - return lanServers; + @WrapWithCondition(method = "updateScreen", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/ServerSelectionList;updateNetworkServers(Ljava/util/List;)V")) + private boolean suppressLanServersOnEssentialTabs(ServerSelectionList self, List lanServers) { + if (!EssentialConfig.INSTANCE.getEssentialFull()) return true; + return EssentialConfig.INSTANCE.getCurrentMultiplayerTab() == 0; } //#if MC>=11600 diff --git a/src/main/java/gg/essential/mixins/transformers/client/gui/MixinGuiPlayerTabOverlay.java b/src/main/java/gg/essential/mixins/transformers/client/gui/MixinGuiPlayerTabOverlay.java index 68f110e..e5a32e2 100644 --- a/src/main/java/gg/essential/mixins/transformers/client/gui/MixinGuiPlayerTabOverlay.java +++ b/src/main/java/gg/essential/mixins/transformers/client/gui/MixinGuiPlayerTabOverlay.java @@ -57,8 +57,13 @@ public class MixinGuiPlayerTabOverlay { //#if MC>=12000 //$$ @ModifyArg(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/DrawContext;drawTextWithShadow(Lnet/minecraft/client/font/TextRenderer;Lnet/minecraft/text/Text;III)I"), index = 2) //$$ private int essential$shiftNameTextAndRenderIcon(TextRenderer textRenderer, Text text, int x, int y, int color, @Local(argsOnly = true) DrawContext context, @Local PlayerListEntry networkPlayerInfo) { - //$$ VertexConsumerProvider.Immediate provider = context.getVertexConsumers(); //$$ UMatrixStack matrixStack = new UMatrixStack(context.getMatrices()); + //#if MC>=12102 + //$$ // FIXME 1.21.2 should not just blindly cast to Immediate, MC now does the draw call for us, needs refactoring + //$$ context.draw(provider -> OnlineIndicator.drawTabIndicatorOuter(matrixStack, (VertexConsumerProvider.Immediate) provider, networkPlayerInfo, x, y)); + //#else + //$$ VertexConsumerProvider.Immediate provider = context.getVertexConsumers(); + //#endif //#else @ModifyArg( method = "renderPlayerlist", @@ -84,6 +89,7 @@ public class MixinGuiPlayerTabOverlay { //#endif //#endif + //#if MC<12102 OnlineIndicator.drawTabIndicatorOuter( matrixStack, //#if MC>=11600 @@ -92,6 +98,7 @@ public class MixinGuiPlayerTabOverlay { networkPlayerInfo, (int) x, (int) y ); + //#endif return x; } diff --git a/src/main/java/gg/essential/mixins/transformers/client/gui/ServerSelectionListAccessor.java b/src/main/java/gg/essential/mixins/transformers/client/gui/ServerSelectionListAccessor.java index b29d6d7..602acfd 100644 --- a/src/main/java/gg/essential/mixins/transformers/client/gui/ServerSelectionListAccessor.java +++ b/src/main/java/gg/essential/mixins/transformers/client/gui/ServerSelectionListAccessor.java @@ -16,13 +16,10 @@ import net.minecraft.client.gui.ServerSelectionList; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.gen.Accessor; +import org.spongepowered.asm.mixin.gen.Invoker; import java.util.List; -//#if MC>=11600 -//$$ import org.spongepowered.asm.mixin.gen.Invoker; -//#endif - @Mixin(ServerSelectionList.class) public interface ServerSelectionListAccessor { @Accessor @@ -31,6 +28,11 @@ public interface ServerSelectionListAccessor { @Accessor List getServerListLan(); + //#if MC<11600 + @Invoker + int invokeGetSize(); + //#endif + //#if MC>=11600 //$$ @Invoker("setList") //$$ void updateList(); diff --git a/src/main/java/gg/essential/mixins/transformers/client/model/Mixin_PlayerEntityRenderStateExt.java b/src/main/java/gg/essential/mixins/transformers/client/model/Mixin_PlayerEntityRenderStateExt.java new file mode 100644 index 0000000..bca6648 --- /dev/null +++ b/src/main/java/gg/essential/mixins/transformers/client/model/Mixin_PlayerEntityRenderStateExt.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.mixins.transformers.client.model; + +import gg.essential.mixins.DummyTarget; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(DummyTarget.class) +public abstract class Mixin_PlayerEntityRenderStateExt { +} diff --git a/src/main/java/gg/essential/mixins/transformers/client/model/Mixin_PlayerEntityRenderStateExt_UpdateRenderState.java b/src/main/java/gg/essential/mixins/transformers/client/model/Mixin_PlayerEntityRenderStateExt_UpdateRenderState.java new file mode 100644 index 0000000..007eb87 --- /dev/null +++ b/src/main/java/gg/essential/mixins/transformers/client/model/Mixin_PlayerEntityRenderStateExt_UpdateRenderState.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.mixins.transformers.client.model; + +import gg.essential.mixins.DummyTarget; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(DummyTarget.class) +public abstract class Mixin_PlayerEntityRenderStateExt_UpdateRenderState { +} diff --git a/src/main/java/gg/essential/mixins/transformers/client/model/ModelPlayerAccessor.java b/src/main/java/gg/essential/mixins/transformers/client/model/ModelPlayerAccessor.java index cfafbfb..c5c1d43 100644 --- a/src/main/java/gg/essential/mixins/transformers/client/model/ModelPlayerAccessor.java +++ b/src/main/java/gg/essential/mixins/transformers/client/model/ModelPlayerAccessor.java @@ -18,9 +18,13 @@ @Mixin(ModelPlayer.class) public interface ModelPlayerAccessor { + //#if MC>=12102 + //$$ // These live in their respective feature renderer now, completely separate from the main player model + //#else @Accessor("bipedCape") ModelRenderer getCape(); @Accessor("bipedDeadmau5Head") ModelRenderer getEars(); + //#endif } diff --git a/src/main/java/gg/essential/mixins/transformers/client/multiplayer/MixinServerData.java b/src/main/java/gg/essential/mixins/transformers/client/multiplayer/MixinServerData.java index df07a8d..797177c 100644 --- a/src/main/java/gg/essential/mixins/transformers/client/multiplayer/MixinServerData.java +++ b/src/main/java/gg/essential/mixins/transformers/client/multiplayer/MixinServerData.java @@ -32,6 +32,9 @@ public abstract class MixinServerData implements ServerDataExt { @Unique private String pingRegion; + @Unique + private Long pingOverride; + @Unique private boolean skipModCompatCheck; @@ -59,6 +62,16 @@ public abstract class MixinServerData implements ServerDataExt { this.pingRegion = pingRegion; } + @Override + public @Nullable Long getEssential$pingOverride() { + return this.pingOverride; + } + + @Override + public void setEssential$pingOverride(@Nullable Long pingOverride) { + this.pingOverride = pingOverride; + } + @Override public boolean getEssential$skipModCompatCheck() { return this.skipModCompatCheck; @@ -86,6 +99,7 @@ private void copyEssentialExt(ServerData from, CallbackInfo ci) { MixinServerData fromExt = (MixinServerData) (Object) from; this.isTrusted = fromExt.isTrusted; this.pingRegion = fromExt.pingRegion; + this.pingOverride = fromExt.pingOverride; this.skipModCompatCheck = fromExt.skipModCompatCheck; this.shareWithFriends = fromExt.shareWithFriends; } diff --git a/src/main/java/gg/essential/mixins/transformers/client/network/Mixin_OverridePing.java b/src/main/java/gg/essential/mixins/transformers/client/network/Mixin_OverridePing.java new file mode 100644 index 0000000..927ed1b --- /dev/null +++ b/src/main/java/gg/essential/mixins/transformers/client/network/Mixin_OverridePing.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.mixins.transformers.client.network; + +import net.minecraft.client.multiplayer.ServerData; +import org.spongepowered.asm.mixin.Final; +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; + +import static gg.essential.mixins.ext.client.multiplayer.ServerDataExtKt.getExt; + +@Mixin(targets = "net/minecraft/client/network/ServerPinger$1") +public class Mixin_OverridePing { + + @Final + @Shadow(aliases = { + "val$server", + "field_3776", + "val$p_147224_1_", + "val$p_105460_", + }) + private ServerData server; + + @Inject( + // FIXME remap bug: should be able to remap these + //#if FABRIC + //#if MC>=12002 + //$$ method = "onPingResult", + //#else + //$$ method = "onPong", + //#endif + //#else + //#if MC>=11700 + //$$ method = "handlePongResponse", + //#else + method = "handlePong", + //#endif + //#endif + at = @At(value = "FIELD", target = "Lnet/minecraft/client/multiplayer/ServerData;pingToServer:J", shift = At.Shift.AFTER) + ) + private void overridePing(CallbackInfo ci) { + Long pingOverride = getExt(this.server).getEssential$pingOverride(); + if (pingOverride != null) { + this.server.pingToServer = pingOverride; + } + } + +} diff --git a/src/main/java/gg/essential/mixins/transformers/client/renderer/MixinEntityRenderer_Zoom.java b/src/main/java/gg/essential/mixins/transformers/client/renderer/MixinEntityRenderer_Zoom.java index d9687f3..cbcf693 100644 --- a/src/main/java/gg/essential/mixins/transformers/client/renderer/MixinEntityRenderer_Zoom.java +++ b/src/main/java/gg/essential/mixins/transformers/client/renderer/MixinEntityRenderer_Zoom.java @@ -19,11 +19,13 @@ @Mixin(EntityRenderer.class) public class MixinEntityRenderer_Zoom { - //#if MC>=11600 - //$$ @ModifyVariable(method = "getFOVModifier", at = @At(value = "STORE", ordinal = 1), ordinal = 0) + private static final String GET_FOV = "getFOVModifier"; + + //#if MC>=11600 && MC<12102 + //$$ @ModifyVariable(method = GET_FOV, at = @At(value = "STORE", ordinal = 1), ordinal = 0) //$$ private double applyZoomModifiers(double f) { //#else - @ModifyVariable(method = "getFOVModifier", at = @At(value = "STORE", ordinal = 1), ordinal = 1) + @ModifyVariable(method = GET_FOV, at = @At(value = "STORE", ordinal = 1), ordinal = 1) private float applyZoomModifiers(float f) { //#endif //noinspection RedundantCast diff --git a/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/MixinRenderPlayer.java b/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/MixinRenderPlayer.java index 0382d02..cec962a 100644 --- a/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/MixinRenderPlayer.java +++ b/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/MixinRenderPlayer.java @@ -27,7 +27,6 @@ import net.minecraft.client.model.ModelPlayer; import gg.essential.handlers.RenderPlayerBypass; import net.minecraft.client.entity.AbstractClientPlayer; -import net.minecraft.client.renderer.entity.RenderManager; import net.minecraft.client.renderer.entity.RenderPlayer; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -40,6 +39,13 @@ import java.util.EnumSet; import java.util.Set; +//#if MC>=12102 +//$$ import gg.essential.mixins.impl.client.model.PlayerEntityRenderStateExt; +//$$ import net.minecraft.client.MinecraftClient; +//$$ import net.minecraft.client.render.entity.state.PlayerEntityRenderState; +//$$ import net.minecraft.util.Identifier; +//#endif + //#if MC>=11700 //$$ import net.minecraft.client.render.entity.EntityRendererFactory.Context; //$$ import net.minecraft.text.Text; @@ -50,7 +56,6 @@ //$$ import net.minecraft.client.renderer.IRenderTypeBuffer; //$$ import net.minecraft.client.renderer.model.ModelRenderer; //#else -import net.minecraft.client.model.ModelBase; //#endif //#if MC<=10809 @@ -67,7 +72,9 @@ @Mixin(RenderPlayer.class) public abstract class MixinRenderPlayer - //#if MC>=11400 + //#if MC>=12102 + //$$ extends LivingEntityRenderer + //#elseif MC>=11400 //$$ extends LivingRenderer> //#elseif MC>=12000 extends RenderLivingBase @@ -84,20 +91,6 @@ public abstract class MixinRenderPlayer @Unique protected EssentialModelRenderer essentialModelRenderer; - //#if MC>=11700 - //$$ public MixinRenderPlayer(Context ctx, PlayerEntityModel model, float shadowRadius) { - //$$ super(ctx, model, shadowRadius); - //$$ } - //#else - //#if MC>=11400 - //$$ public MixinRenderPlayer(EntityRendererManager renderManagerIn, PlayerModel modelBaseIn, float shadowSizeIn) { - //#else - public MixinRenderPlayer(RenderManager renderManagerIn, ModelBase modelBaseIn, float shadowSizeIn) { - //#endif - super(renderManagerIn, modelBaseIn, shadowSizeIn); - } - //#endif - //#if MC>=11700 //$$ @Inject(method = "", at = @At("RETURN")) //#else @@ -113,6 +106,18 @@ private void initEssentialCosmeticsLayer(CallbackInfo ci) { return this.layerRenderers; } + //#if MC>=12102 + //$$ @Inject(method = "updateRenderState(Lnet/minecraft/client/network/AbstractClientPlayerEntity;Lnet/minecraft/client/render/entity/state/PlayerEntityRenderState;F)V", at = @At("RETURN")) + //$$ private void disableOuterLayerWhereCoveredByCosmetic(AbstractClientPlayerEntity player, PlayerEntityRenderState state, float tickDelta, CallbackInfo ci) { + //$$ Set coveredLayers = ((AbstractClientPlayerExt) player).getCosmeticsState().getCoveredLayers(); + //$$ state.hatVisible &= !coveredLayers.contains(SkinLayer.HAT); + //$$ state.jacketVisible &= !coveredLayers.contains(SkinLayer.JACKET); + //$$ state.leftSleeveVisible &= !coveredLayers.contains(SkinLayer.LEFT_SLEEVE); + //$$ state.rightSleeveVisible &= !coveredLayers.contains(SkinLayer.RIGHT_SLEEVE); + //$$ state.leftPantsLegVisible &= !coveredLayers.contains(SkinLayer.LEFT_PANTS_LEG); + //$$ state.rightPantsLegVisible &= !coveredLayers.contains(SkinLayer.RIGHT_PANTS_LEG); + //$$ } + //#else @Inject(method = "setModelVisibilities", at = @At("RETURN")) private void disableOuterLayerWhereCoveredByCosmetic(AbstractClientPlayer player, CallbackInfo ci) { Set coveredLayers = ((AbstractClientPlayerExt) player).getCosmeticsState().getCoveredLayers(); @@ -124,6 +129,7 @@ private void disableOuterLayerWhereCoveredByCosmetic(AbstractClientPlayer player model.bipedLeftLegwear.showModel &= !coveredLayers.contains(SkinLayer.LEFT_PANTS_LEG); model.bipedRightLegwear.showModel &= !coveredLayers.contains(SkinLayer.RIGHT_PANTS_LEG); } + //#endif //#if FORGE && MC<11700 @Redirect( @@ -147,7 +153,13 @@ private void isRenderingLeftArm(CallbackInfo ci) { @Inject(method = "renderLeftArm", at = @At("RETURN")) //#if MC>=11400 + //#if MC>=12102 + //$$ private void renderLeftArm(MatrixStack vMatrixStack, VertexConsumerProvider buffers, int combinedLight, Identifier skinTexture, boolean sleeveVisible, CallbackInfo ci) { + //$$ AbstractClientPlayerEntity player = MinecraftClient.getInstance().player; + //$$ if (player == null) return; + //#else //$$ private void renderLeftArm(MatrixStack vMatrixStack, IRenderTypeBuffer buffers, int combinedLight, AbstractClientPlayerEntity player, CallbackInfo ci) { + //#endif //$$ UMatrixStack matrixStack = new UMatrixStack(vMatrixStack); //$$ RenderBackend.VertexConsumerProvider vertexConsumerProvider = new MinecraftRenderBackend.VertexConsumerProvider(buffers, combinedLight); //#else @@ -155,7 +167,9 @@ private void renderLeftArm(AbstractClientPlayer player, CallbackInfo ci) { UMatrixStack matrixStack = new UMatrixStack(); RenderBackend.VertexConsumerProvider vertexConsumerProvider = new MinecraftRenderBackend.VertexConsumerProvider(); //#endif + //#if MC<12102 getMainModel().isChild = false; + //#endif essentialModelRenderer.render(matrixStack, vertexConsumerProvider, EnumSet.of(EnumPart.LEFT_ARM), player); EmoteWheel.isPlayerArmRendering = false; } @@ -167,7 +181,13 @@ private void isRenderingRightArm(CallbackInfo ci) { @Inject(method = "renderRightArm", at = @At("RETURN")) //#if MC>=11400 + //#if MC>=12102 + //$$ private void renderRightArm(MatrixStack vMatrixStack, VertexConsumerProvider buffers, int combinedLight, Identifier skinTexture, boolean sleeveVisible, CallbackInfo ci) { + //$$ AbstractClientPlayerEntity player = MinecraftClient.getInstance().player; + //$$ if (player == null) return; + //#else //$$ private void renderRightArm(MatrixStack vMatrixStack, IRenderTypeBuffer buffers, int combinedLight, AbstractClientPlayerEntity player, CallbackInfo ci) { + //#endif //$$ UMatrixStack matrixStack = new UMatrixStack(vMatrixStack); //$$ RenderBackend.VertexConsumerProvider vertexConsumerProvider = new MinecraftRenderBackend.VertexConsumerProvider(buffers, combinedLight); //#else @@ -175,11 +195,19 @@ private void renderRightArm(AbstractClientPlayer player, CallbackInfo ci) { UMatrixStack matrixStack = new UMatrixStack(); RenderBackend.VertexConsumerProvider vertexConsumerProvider = new MinecraftRenderBackend.VertexConsumerProvider(); //#endif + //#if MC<12102 getMainModel().isChild = false; + //#endif essentialModelRenderer.render(matrixStack, vertexConsumerProvider, EnumSet.of(EnumPart.RIGHT_ARM), player); EmoteWheel.isPlayerArmRendering = false; } + //#if MC>=12102 + //$$ @Inject(method = "renderLabelIfPresent(Lnet/minecraft/client/render/entity/state/PlayerEntityRenderState;Lnet/minecraft/text/Text;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;I)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/entity/LivingEntityRenderer;renderLabelIfPresent(Lnet/minecraft/client/render/entity/state/EntityRenderState;Lnet/minecraft/text/Text;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;I)V", ordinal = 1)) + //$$ private void setNametagEntity(CallbackInfo ci, @Local(argsOnly = true) PlayerEntityRenderState state) { + //$$ OnlineIndicator.nametagEntity = ((PlayerEntityRenderStateExt) state).essential$getEntity(); + //$$ } + //#else //#if MC>=12005 //$$ @Inject(method = "renderLabelIfPresent(Lnet/minecraft/client/network/AbstractClientPlayerEntity;Lnet/minecraft/text/Text;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;IF)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/entity/LivingEntityRenderer;renderLabelIfPresent(Lnet/minecraft/entity/Entity;Lnet/minecraft/text/Text;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;IF)V", ordinal = 1)) //#elseif MC>=11600 @@ -192,17 +220,25 @@ private void renderRightArm(AbstractClientPlayer player, CallbackInfo ci) { private void setNametagEntity(CallbackInfo ci, @Local(argsOnly = true) AbstractClientPlayer entityIn) { OnlineIndicator.nametagEntity = entityIn; } + //#endif @Override public Mat4 essential$getTransform(AbstractClientPlayer player, float yaw, float partialTicks) { + // FIXME 1.21.2 this is getting unreasonably expensive //#if MC>=11400 //$$ MatrixStack stack = new MatrixStack(); + //#if MC>=12102 + //$$ PlayerEntityRenderState state = this.createRenderState(); + //$$ this.updateRenderState(player, state, partialTicks); + //$$ this.setupTransforms(state, stack, state.bodyYaw, state.baseScale); + //#else //$$ this.applyRotations( //$$ player, stack, player.ticksExisted + partialTicks, yaw, partialTicks //#if MC>=12006 //$$ , player.getScale() //#endif //$$ ); + //#endif //$$ return GLUtil.INSTANCE.glGetMatrix(stack, 1f); //#else return new UMatrixStack().runReplacingGlobalState(() -> { @@ -212,6 +248,7 @@ private void setNametagEntity(CallbackInfo ci, @Local(argsOnly = true) AbstractC //#endif } + //#if MC<12102 @Shadow protected abstract void applyRotations( AbstractClientPlayer player, //#if MC>=11400 @@ -224,4 +261,7 @@ private void setNametagEntity(CallbackInfo ci, @Local(argsOnly = true) AbstractC //$$ , float scale //#endif ); + //#endif + + public MixinRenderPlayer() { super(null, null, 0f); } } diff --git a/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/MixinRendererLivingEntity.java b/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/MixinRendererLivingEntity.java index c73178e..122605a 100644 --- a/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/MixinRendererLivingEntity.java +++ b/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/MixinRendererLivingEntity.java @@ -27,6 +27,10 @@ import net.minecraft.client.renderer.entity.RenderLivingBase; //#endif +//#if MC>=12102 +//$$ import net.minecraft.client.render.entity.state.LivingEntityRenderState; +//#endif + //#if MC>=11700 //$$ import net.minecraft.client.render.entity.EntityRendererFactory; //#endif @@ -37,13 +41,22 @@ @Mixin(value = RenderLivingBase.class, priority = 500) //#endif +//#if MC>=12102 +//$$ public abstract class MixinRendererLivingEntity extends EntityRenderer { +//#else public abstract class MixinRendererLivingEntity extends Render { +//#endif protected MixinRendererLivingEntity() { super(null); } + //#if MC>=12102 + //$$ @Inject(method = "hasLabel(Lnet/minecraft/entity/LivingEntity;D)Z", at = @At("HEAD"), cancellable = true) + //$$ private void canRenderNameOfEmulatedPlayer(T entity, double squaredDistanceToCamera, CallbackInfoReturnable ci) { + //#else @Inject(method = "canRenderName(Lnet/minecraft/entity/EntityLivingBase;)Z", at = @At("HEAD"), cancellable = true) private void canRenderNameOfEmulatedPlayer(T entity, CallbackInfoReturnable ci) { + //#endif UI3DPlayer component = UI3DPlayer.current; if (component != null) { ci.setReturnValue(!component.getHideNameTags().get()); diff --git a/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_ApplyPoseTransform.java b/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_ApplyPoseTransform.java index 8eb9fd0..2fe0350 100644 --- a/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_ApplyPoseTransform.java +++ b/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_ApplyPoseTransform.java @@ -11,6 +11,7 @@ */ package gg.essential.mixins.transformers.client.renderer.entity; +import com.llamalad7.mixinextras.sugar.Local; import gg.essential.mixins.impl.client.model.ModelBipedExt; import gg.essential.mixins.impl.client.model.ModelBipedUtil; import gg.essential.model.backend.PlayerPose; @@ -22,6 +23,11 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +//#if MC>=12102 +//$$ import gg.essential.mixins.impl.client.model.PlayerEntityRenderStateExt; +//$$ import net.minecraft.client.render.entity.state.BipedEntityRenderState; +//#endif + @Mixin(ModelBiped.class) public abstract class Mixin_ApplyPoseTransform implements ModelBipedExt { @@ -53,22 +59,25 @@ private void resetPose(CallbackInfo ci) { ModelBipedUtil.resetPose((ModelBiped) (Object) this); } + //#if MC>=12102 + //$$ @Inject(method = "setAngles", at = @At("RETURN")) + //#else @Inject(method = "setRotationAngles", at = @At(value = "INVOKE", target = COPY_MODEL_ANGLES)) + //#endif private void applyPoseTransform( - //#if MC>=11400 - //$$ net.minecraft.entity.LivingEntity entity, + CallbackInfo ci, + //#if MC>=12102 + //$$ @Local(argsOnly = true) BipedEntityRenderState state + //#elseif MC>=11400 + //$$ @Local(argsOnly = true) net.minecraft.entity.LivingEntity entity + //#else + @Local(argsOnly = true) Entity entity //#endif - float limbSwing, - float limbSwingAmount, - float ageInTicks, - float netHeadYaw, - float headPitch, - //#if MC<11400 - float scaleFactor, - Entity entity, - //#endif - CallbackInfo ci ) { + //#if MC>=12102 + //$$ if (!(state instanceof PlayerEntityRenderStateExt)) return; + //$$ Entity entity = ((PlayerEntityRenderStateExt) state).essential$getEntity(); + //#endif ModelBipedUtil.applyPoseTransform((ModelBiped) (Object) this, entity); } } diff --git a/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_ApplyPoseTransform_Elytra.java b/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_ApplyPoseTransform_Elytra.java index 8c22043..b9788c5 100644 --- a/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_ApplyPoseTransform_Elytra.java +++ b/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_ApplyPoseTransform_Elytra.java @@ -16,6 +16,7 @@ import gg.essential.gui.common.EmulatedUI3DPlayer; import gg.essential.mixins.impl.client.entity.AbstractClientPlayerExt; import gg.essential.mixins.impl.client.model.ElytraPoseSupplier; +import gg.essential.mixins.impl.client.model.PlayerEntityRenderStateExt; import gg.essential.model.backend.PlayerPose; import gg.essential.model.backend.minecraft.PlayerPoseKt; import net.minecraft.client.entity.AbstractClientPlayer; @@ -33,6 +34,11 @@ import static gg.essential.model.backend.minecraft.PlayerPoseKt.toPose; +//#if MC>=12102 +//$$ import gg.essential.mixins.impl.client.model.PlayerEntityRenderStateExt; +//$$ import net.minecraft.client.render.entity.state.BipedEntityRenderState; +//#endif + @Mixin(ModelElytra.class) public abstract class Mixin_ApplyPoseTransform_Elytra implements ElytraPoseSupplier { @@ -54,47 +60,51 @@ public abstract class Mixin_ApplyPoseTransform_Elytra implements ElytraPoseSuppl @Inject(method = "setRotationAngles", at = @At("HEAD")) private void resetPose( CallbackInfo ci, - //#if MC>=11400 + //#if MC>=12102 + //$$ @Local(argsOnly = true) BipedEntityRenderState state + //#elseif MC>=11400 //$$ @Local(argsOnly = true) net.minecraft.entity.LivingEntity entity //#else @Local(argsOnly = true) Entity entity //#endif ) { + //#if MC>=12102 + //$$ if (!(state instanceof PlayerEntityRenderStateExt)) return; + //$$ AbstractClientPlayerEntity player = ((PlayerEntityRenderStateExt) state).essential$getEntity(); + //#else if (!(entity instanceof AbstractClientPlayer)) return; AbstractClientPlayer player = (AbstractClientPlayer) entity; - ModelElytra model = (ModelElytra) (Object) this; + //#endif if (resetPose == null) { resetPose = PlayerPoseKt.withElytraPose(PlayerPose.Companion.neutral(), this.leftWing, this.rightWing, player); } else { - boolean isChild = model.isChild; // child isn't set by the method we inject into, so we need to preserve it PlayerPoseKt.applyElytraPose(resetPose, this.leftWing, this.rightWing, player); - model.isChild = isChild; } } @Inject(method = "setRotationAngles", at = @At("TAIL")) private void applyPoseTransform( - //#if MC>=11400 - //$$ net.minecraft.entity.LivingEntity entity, - //#endif - float limbSwing, - float limbSwingAmount, - float ageInTicks, - float netHeadYaw, - float headPitch, - //#if MC<11400 - float scaleFactor, - Entity entity, + CallbackInfo ci, + //#if MC>=12102 + //$$ @Local(argsOnly = true) BipedEntityRenderState state + //#elseif MC>=11400 + //$$ @Local(argsOnly = true) net.minecraft.entity.LivingEntity entity + //#else + @Local(argsOnly = true) Entity entity //#endif - CallbackInfo ci ) { + //#if MC>=12102 + //$$ if (!(state instanceof PlayerEntityRenderStateExt)) return; + //$$ AbstractClientPlayerEntity player = ((PlayerEntityRenderStateExt) state).essential$getEntity(); + //#else if (!(entity instanceof AbstractClientPlayerExt)) return; - if (EssentialConfig.INSTANCE.getDisableEmotes() && !(entity instanceof EmulatedUI3DPlayer.EmulatedPlayer)) { + AbstractClientPlayer player = (AbstractClientPlayer) entity; + //#endif + if (EssentialConfig.INSTANCE.getDisableEmotes() && !(player instanceof EmulatedUI3DPlayer.EmulatedPlayer)) { return; } - AbstractClientPlayer player = (AbstractClientPlayer) entity; - AbstractClientPlayerExt playerExt = (AbstractClientPlayerExt) entity; + AbstractClientPlayerExt playerExt = (AbstractClientPlayerExt) player; PlayerPose basePose = PlayerPoseKt.withElytraPose(PlayerPose.Companion.neutral(), this.leftWing, this.rightWing, player); PlayerPose transformedPose = playerExt.getPoseManager().computePose(playerExt.getWearablesManager(), basePose); diff --git a/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_ApplyPoseTransform_EntityOnShoulder.java b/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_ApplyPoseTransform_EntityOnShoulder.java index a3a524c..f692426 100644 --- a/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_ApplyPoseTransform_EntityOnShoulder.java +++ b/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_ApplyPoseTransform_EntityOnShoulder.java @@ -11,6 +11,7 @@ */ package gg.essential.mixins.transformers.client.renderer.entity; +import com.llamalad7.mixinextras.sugar.Local; import dev.folomeev.kotgl.matrix.matrices.mutables.MutableMat4; import gg.essential.config.EssentialConfig; import gg.essential.gui.common.EmulatedUI3DPlayer; @@ -28,13 +29,20 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Surrogate; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import static dev.folomeev.kotgl.matrix.matrices.Matrices.identityMat3; import static dev.folomeev.kotgl.matrix.matrices.Matrices.identityMat4; import static dev.folomeev.kotgl.matrix.matrices.mutables.MutableMatrices.timesSelf; +//#if MC>=12102 +//$$ import gg.essential.mixins.impl.client.model.PlayerEntityRenderStateExt; +//$$ import net.minecraft.client.render.entity.state.PlayerEntityRenderState; +//#endif + //#if MC>=11600 //$$ import gg.essential.mixins.impl.util.math.Matrix4fExt; +//$$ import com.mojang.blaze3d.matrix.MatrixStack; //$$ import static dev.folomeev.kotgl.matrix.matrices.mutables.MutableMatrices.times; //#endif @@ -81,6 +89,22 @@ private void applyPoseTransform( //#endif } + //#if MC>=12102 + //$$ @Inject( + //$$ method = "render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/client/render/entity/state/PlayerEntityRenderState;Lnet/minecraft/entity/passive/ParrotEntity$Variant;FFZ)V", + //$$ at = @At(value = "INVOKE", target = "Lnet/minecraft/client/util/math/MatrixStack;push()V", shift = At.Shift.AFTER) + //$$ ) + //$$ private void applyPoseTransform( + //$$ CallbackInfo ci, + //$$ @Local(argsOnly = true) MatrixStack matrixStack, + //$$ @Local(argsOnly = true) PlayerEntityRenderState state, + //$$ @Local(argsOnly = true) boolean leftSide + //$$ ) { + //$$ PlayerEntity player = ((PlayerEntityRenderStateExt) state).essential$getEntity(); + //$$ if (player == null) return; + //$$ applyPoseTransform(player, leftSide, matrixStack); + //$$ } + //#else //#if MC>=11600 //$$ @Inject(method = { //#if FABRIC @@ -207,4 +231,5 @@ private void applyPoseTransform( //$$ applyPoseTransform(player, leftSide, matrixStack); //$$ } //#endif + //#endif } diff --git a/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_CosmeticHoverOutline_Cape.java b/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_CosmeticHoverOutline_Cape.java index 71b5b71..1d6372e 100644 --- a/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_CosmeticHoverOutline_Cape.java +++ b/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_CosmeticHoverOutline_Cape.java @@ -29,6 +29,11 @@ import static gg.essential.mod.cosmetics.CapeDisabledKt.CAPE_DISABLED_COSMETIC; +//#if MC>=12102 +//$$ import gg.essential.mixins.impl.client.model.PlayerEntityRenderStateExt; +//$$ import net.minecraft.client.render.entity.state.PlayerEntityRenderState; +//#endif + //#if MC>=11600 //$$ import com.mojang.blaze3d.matrix.MatrixStack; //$$ import net.minecraft.client.renderer.IRenderTypeBuffer; @@ -39,12 +44,16 @@ // For fallback renderer see renderThirdPartyCapeForHoverOutline @Mixin(LayerCape.class) public abstract class Mixin_CosmeticHoverOutline_Cape - //#if MC>=11600 + //#if MC>=12102 + //$$ extends FeatureRenderer + //#elseif MC>=11600 //$$ extends LayerRenderer> //#endif { - //#if MC>=11600 + //#if MC>=12102 + //$$ private static final String RENDER_LAYER = "render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/client/render/entity/state/PlayerEntityRenderState;FF)V"; + //#elseif MC>=11600 //$$ private static final String RENDER_LAYER = "render(Lcom/mojang/blaze3d/matrix/MatrixStack;Lnet/minecraft/client/renderer/IRenderTypeBuffer;ILnet/minecraft/client/entity/player/AbstractClientPlayerEntity;FFFFFF)V"; //#else private static final String RENDER_LAYER = "doRenderLayer(Lnet/minecraft/client/entity/AbstractClientPlayer;FFFFFFF)V"; @@ -60,11 +69,15 @@ private void renderIntoHoverOutlineFrameBuffer( //$$ IRenderTypeBuffer buffer, //$$ int light, //#endif + //#if MC>=12102 + //$$ PlayerEntityRenderState state, + //#else AbstractClientPlayer player, float limbSwing, float limbSwingAmount, float partialTicks, float ageInTicks, + //#endif float netHeadYaw, float headPitch, //#if MC<11400 @@ -77,7 +90,11 @@ private void renderIntoHoverOutlineFrameBuffer( return; } + //#if MC>=12102 + //$$ AbstractClientPlayerExt playerExt = (AbstractClientPlayerExt) ((PlayerEntityRenderStateExt) state).essential$getEntity(); + //#else AbstractClientPlayerExt playerExt = (AbstractClientPlayerExt) player; + //#endif CosmeticsState cosmeticsState = playerExt.getCosmeticsState(); EquippedCosmetic equippedCosmetic = cosmeticsState.getCosmetics().get(CosmeticSlot.CAPE); Cosmetic cosmetic = equippedCosmetic != null ? equippedCosmetic.getCosmetic() : null; @@ -93,7 +110,9 @@ private void renderIntoHoverOutlineFrameBuffer( //#endif outlineEffect.allocOutlineBuffer(cosmetic).use(() -> { - //#if MC>=11400 + //#if MC>=12102 + //$$ render(matrixStack, buffer, light, state, netHeadYaw, headPitch); + //#elseif MC>=11400 //$$ render(matrixStack, buffer, light, player, limbSwing, limbSwingAmount, partialTicks, ageInTicks, netHeadYaw, headPitch); //#else doRenderLayer(player, limbSwing, limbSwingAmount, partialTicks, ageInTicks, netHeadYaw, headPitch, scale); @@ -121,11 +140,15 @@ public abstract void doRenderLayer( //$$ IRenderTypeBuffer buffer, //$$ int light, //#endif + //#if MC>=12102 + //$$ PlayerEntityRenderState state, + //#else AbstractClientPlayer player, float limbSwing, float limbSwingAmount, float partialTicks, float ageInTicks, + //#endif float netHeadYaw, float headPitch //#if MC<11400 diff --git a/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_DisableArmorRendering.java b/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_DisableArmorRendering.java index c2d6b7a..5c0e1ea 100644 --- a/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_DisableArmorRendering.java +++ b/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_DisableArmorRendering.java @@ -11,6 +11,7 @@ */ package gg.essential.mixins.transformers.client.renderer.entity; +import com.llamalad7.mixinextras.sugar.Local; import gg.essential.mixins.impl.client.renderer.entity.ArmorRenderingUtil; import net.minecraft.client.renderer.entity.layers.LayerArmorBase; import net.minecraft.entity.EntityLivingBase; @@ -19,18 +20,18 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -//#if MC==11202 +//#if MC>=11200 import net.minecraft.inventory.EntityEquipmentSlot; //#endif @Mixin(value = LayerArmorBase.class) public class Mixin_DisableArmorRendering { - //#if MC==11202 + //#if MC>=11200 @Inject(method = "renderArmorLayer", at = @At(value = "HEAD"), cancellable = true) - private void essential$disableArmorRendering(EntityLivingBase entityLivingBaseIn, float limbSwing, float limbSwingAmount, float partialTicks, float ageInTicks, float netHeadYaw, float headPitch, float scale, EntityEquipmentSlot slotIn, CallbackInfo info) { + private void essential$disableArmorRendering(CallbackInfo info, @Local(argsOnly = true) EntityLivingBase entityLivingBaseIn, @Local(argsOnly = true) EntityEquipmentSlot slotIn) { int slotIndex = slotIn.getIndex(); - //#else if MC==10809 + //#else //$$ @Inject(method = "renderLayer", at = @At(value = "HEAD"), cancellable = true) //$$ private void essential$disableArmorRendering(EntityLivingBase entityLivingBaseIn, float limbSwing, float limbSwingAmount, float partialTicks, float ageInTicks, float netHeadYaw, float headPitch, float scale, int slotIn, CallbackInfo info) { //$$ int slotIndex = slotIn-1; diff --git a/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_DisableElytraRendering.java b/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_DisableElytraRendering.java index 50602b0..d215fb6 100644 --- a/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_DisableElytraRendering.java +++ b/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_DisableElytraRendering.java @@ -11,6 +11,7 @@ */ package gg.essential.mixins.transformers.client.renderer.entity; +import com.llamalad7.mixinextras.sugar.Local; import gg.essential.mixins.impl.client.renderer.entity.ArmorRenderingUtil; import net.minecraft.client.renderer.entity.layers.LayerElytra; import net.minecraft.entity.EntityLivingBase; @@ -20,12 +21,34 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +//#if MC>=12102 +//$$ import gg.essential.mixins.impl.client.model.PlayerEntityRenderStateExt; +//$$ import net.minecraft.client.render.entity.state.BipedEntityRenderState; +//#endif + @Mixin(value = LayerElytra.class) public class Mixin_DisableElytraRendering { - @Inject(method = "doRenderLayer", at = @At(value = "HEAD"), cancellable = true) - private void essential$disableElytraRendering(EntityLivingBase entitylivingbaseIn, float limbSwing, float limbSwingAmount, float partialTicks, float ageInTicks, float netHeadYaw, float headPitch, float scale, CallbackInfo info) { - if (ArmorRenderingUtil.shouldDisableArmor(entitylivingbaseIn, EntityEquipmentSlot.CHEST.getIndex())) { + //#if MC>=11600 + //$$ private static final String RENDER_LAYER = "render"; + //#else + private static final String RENDER_LAYER = "doRenderLayer"; + //#endif + + @Inject(method = RENDER_LAYER, at = @At(value = "HEAD"), cancellable = true) + private void essential$disableElytraRendering( + CallbackInfo info, + //#if MC>=12102 + //$$ @Local(argsOnly = true) BipedEntityRenderState state + //#else + @Local(argsOnly = true) EntityLivingBase entity + //#endif + ) { + //#if MC>=12102 + //$$ if (!(state instanceof PlayerEntityRenderStateExt)) return; + //$$ LivingEntity entity = ((PlayerEntityRenderStateExt) state).essential$getEntity(); + //#endif + if (ArmorRenderingUtil.shouldDisableArmor(entity, EntityEquipmentSlot.CHEST.getIndex())) { info.cancel(); } } diff --git a/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_DisablePlayerSkullRendering.java b/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_DisablePlayerSkullRendering.java index 5d0c97f..97f88fb 100644 --- a/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_DisablePlayerSkullRendering.java +++ b/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_DisablePlayerSkullRendering.java @@ -11,6 +11,7 @@ */ package gg.essential.mixins.transformers.client.renderer.entity; +import com.llamalad7.mixinextras.sugar.Local; import gg.essential.mixins.impl.client.renderer.entity.ArmorRenderingUtil; import net.minecraft.client.renderer.entity.layers.LayerCustomHead; import net.minecraft.entity.EntityLivingBase; @@ -19,13 +20,13 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -//#if MC>10809 -import net.minecraft.inventory.EntityEquipmentSlot; +//#if MC>=12102 +//$$ import gg.essential.mixins.impl.client.model.PlayerEntityRenderStateExt; +//$$ import net.minecraft.client.render.entity.state.LivingEntityRenderState; //#endif -//#if MC>=11600 -//$$ import com.mojang.blaze3d.matrix.MatrixStack; -//$$ import net.minecraft.client.renderer.IRenderTypeBuffer; +//#if MC>10809 +import net.minecraft.inventory.EntityEquipmentSlot; //#endif /** @@ -37,7 +38,9 @@ */ @Mixin(LayerCustomHead.class) public abstract class Mixin_DisablePlayerSkullRendering { - //#if MC>=11400 + //#if MC>=12102 + //$$ private static final String RENDER_TARGET = "render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/client/render/entity/state/LivingEntityRenderState;FF)V"; + //#elseif MC>=11400 //$$ private static final String RENDER_TARGET = "render(Lcom/mojang/blaze3d/matrix/MatrixStack;Lnet/minecraft/client/renderer/IRenderTypeBuffer;ILnet/minecraft/entity/LivingEntity;FFFFFF)V"; //#else private static final String RENDER_TARGET = "doRenderLayer"; @@ -45,23 +48,17 @@ public abstract class Mixin_DisablePlayerSkullRendering { @Inject(method = RENDER_TARGET, at = @At(value = "HEAD"), cancellable = true) private void essential$disableArmorRendering( - //#if MC>=11400 - //$$ MatrixStack matrixStack, - //$$ IRenderTypeBuffer buffer, - //$$ int light, - //#endif - EntityLivingBase entity, - float limbSwing, - float limbSwingAmount, - float partialTicks, - float ageInTicks, - float netHeadYaw, - float headPitch, - //#if MC<11400 - float scale, + CallbackInfo ci, + //#if MC>=12102 + //$$ @Local(argsOnly = true) LivingEntityRenderState state + //#else + @Local(argsOnly = true) EntityLivingBase entity //#endif - CallbackInfo ci ) { + //#if MC>=12102 + //$$ if (!(state instanceof PlayerEntityRenderStateExt)) return; + //$$ LivingEntity entity = ((PlayerEntityRenderStateExt) state).essential$getEntity(); + //#endif //#if MC<=10809 //$$ int headSlotIndex = 2; // The slot for HEAD is 3, but we need to remove 1 to get the index. //#else diff --git a/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_ElytraPoseSupplier.java b/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_ElytraPoseSupplier.java index 1bd4284..a3918aa 100644 --- a/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_ElytraPoseSupplier.java +++ b/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_ElytraPoseSupplier.java @@ -30,10 +30,22 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +//#if MC>=12102 +//$$ import gg.essential.mixins.impl.client.model.PlayerEntityRenderStateExt; +//$$ import gg.essential.mixins.transformers.client.renderer.entity.equipment.EquipmentRendererAccessor; +//$$ import net.minecraft.client.render.entity.equipment.EquipmentRenderer; +//$$ import net.minecraft.client.render.entity.state.BipedEntityRenderState; +//$$ import net.minecraft.item.equipment.EquipmentModel; +//$$ import net.minecraft.util.Identifier; +//#endif + @Mixin(LayerElytra.class) public abstract class Mixin_ElytraPoseSupplier implements ElytraPoseSupplier { - //#if MC>=11600 + //#if MC>=12102 + //$$ private static final String RENDER_LAYER = "render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/client/render/entity/state/BipedEntityRenderState;FF)V"; + //$$ private static final String MODEL_RENDER = "Lnet/minecraft/client/render/entity/equipment/EquipmentRenderer;render(Lnet/minecraft/item/equipment/EquipmentModel$LayerType;Lnet/minecraft/util/Identifier;Lnet/minecraft/client/model/Model;Lnet/minecraft/item/ItemStack;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/util/Identifier;)V"; + //#elseif MC>=11600 //$$ private static final String RENDER_LAYER = "render(Lcom/mojang/blaze3d/matrix/MatrixStack;Lnet/minecraft/client/renderer/IRenderTypeBuffer;ILnet/minecraft/entity/LivingEntity;FFFFFF)V"; //#if MC>=12100 //$$ private static final String MODEL_RENDER = "Lnet/minecraft/client/render/entity/model/ElytraEntityModel;render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumer;II)V"; @@ -45,6 +57,9 @@ public abstract class Mixin_ElytraPoseSupplier implements ElytraPoseSupplier { private static final String MODEL_RENDER = "Lnet/minecraft/client/model/ModelElytra;render(Lnet/minecraft/entity/Entity;FFFFFF)V"; //#endif + //#if MC>=12102 + //$$ @Shadow @Final private EquipmentRenderer equipmentRenderer; + //#endif @Shadow @Final private ModelElytra modelElytra; @Unique @@ -60,9 +75,26 @@ private void unsetRenderedPose(CallbackInfo ci) { } @Inject(method = RENDER_LAYER, at = @At(value = "INVOKE", target = MODEL_RENDER)) + //#if MC>=12102 + //$$ private void setRenderedPose( + //$$ CallbackInfo ci, + //$$ @Local(argsOnly = true) BipedEntityRenderState state, + //$$ @Local(ordinal = 1) Identifier modelId + //$$ ) { + //$$ if (!(state instanceof PlayerEntityRenderStateExt)) return; + //$$ AbstractClientPlayerEntity player = ((PlayerEntityRenderStateExt) state).essential$getEntity(); + //$$ + //$$ boolean modelHasNoWings = ((EquipmentRendererAccessor) this.equipmentRenderer) + //$$ .getEquipmentModelLoader() + //$$ .get(modelId) + //$$ .getLayers(EquipmentModel.LayerType.WINGS) + //$$ .isEmpty(); + //$$ if (modelHasNoWings) return; + //#else private void setRenderedPose(CallbackInfo ci, @Local(argsOnly = true) EntityLivingBase entity) { if (!(entity instanceof AbstractClientPlayer)) return; AbstractClientPlayer player = (AbstractClientPlayer) entity; + //#endif this.leftWingPose = ((ElytraPoseSupplier) this.modelElytra).getLeftWingPose(); this.rightWingPose = ((ElytraPoseSupplier) this.modelElytra).getRightWingPose(); diff --git a/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_Emissive_Cape.java b/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_Emissive_Cape.java index 429c640..92efba4 100644 --- a/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_Emissive_Cape.java +++ b/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_Emissive_Cape.java @@ -28,6 +28,12 @@ import static gg.essential.util.UIdentifierKt.toMC; +//#if MC>=12102 +//$$ import gg.essential.mixins.impl.client.model.PlayerEntityRenderStateExt; +//$$ import net.minecraft.client.render.entity.model.BipedEntityModel; +//$$ import net.minecraft.client.render.entity.state.PlayerEntityRenderState; +//#endif + //#if MC>=11600 //$$ import com.mojang.blaze3d.matrix.MatrixStack; //$$ import com.mojang.blaze3d.vertex.IVertexBuilder; @@ -38,7 +44,10 @@ @Mixin(LayerCape.class) public abstract class Mixin_Emissive_Cape { - //#if MC>=11600 + //#if MC>=12102 + //$$ private static final String RENDER_LAYER = "render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/client/render/entity/state/PlayerEntityRenderState;FF)V"; + //$$ private static final String RENDER_CAPE = "Lnet/minecraft/client/render/entity/model/BipedEntityModel;render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumer;II)V"; + //#elseif MC>=11600 //$$ private static final String RENDER_LAYER = "render(Lcom/mojang/blaze3d/matrix/MatrixStack;Lnet/minecraft/client/renderer/IRenderTypeBuffer;ILnet/minecraft/client/entity/player/AbstractClientPlayerEntity;FFFFFF)V"; //$$ private static final String RENDER_CAPE = "Lnet/minecraft/client/renderer/entity/model/PlayerModel;renderCape(Lcom/mojang/blaze3d/matrix/MatrixStack;Lcom/mojang/blaze3d/vertex/IVertexBuilder;II)V"; //#else @@ -48,7 +57,11 @@ public abstract class Mixin_Emissive_Cape { @WrapOperation(method = RENDER_LAYER, at = @At(value = "INVOKE", target = RENDER_CAPE)) private void renderWithEmissiveLayer( + //#if MC>=12102 + //$$ BipedEntityModel model, + //#else ModelPlayer model, + //#endif //#if MC>=11400 //$$ MatrixStack matrixStack, //$$ IVertexBuilder vertexConsumer, @@ -61,7 +74,11 @@ private void renderWithEmissiveLayer( //#if MC>=11400 //$$ @Local(argsOnly = true) IRenderTypeBuffer buffer, //#endif + //#if MC>=12102 + //$$ @Local(argsOnly = true) PlayerEntityRenderState state + //#else @Local(argsOnly = true) AbstractClientPlayer player + //#endif ) { // Regular cape original.call( @@ -77,7 +94,11 @@ private void renderWithEmissiveLayer( ); // Emissive layer + //#if MC>=12102 + //$$ AbstractClientPlayerExt playerExt = (AbstractClientPlayerExt) ((PlayerEntityRenderStateExt) state).essential$getEntity(); + //#else AbstractClientPlayerExt playerExt = (AbstractClientPlayerExt) player; + //#endif UIdentifier emissiveTexture = playerExt.getEmissiveCapeTexture(); if (emissiveTexture == null) { return; diff --git a/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/equipment/EquipmentRendererAccessor.java b/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/equipment/EquipmentRendererAccessor.java new file mode 100644 index 0000000..25ff924 --- /dev/null +++ b/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/equipment/EquipmentRendererAccessor.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.mixins.transformers.client.renderer.entity.equipment; + +import gg.essential.mixins.DummyTarget; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(DummyTarget.class) +public interface EquipmentRendererAccessor { +} diff --git a/src/main/java/gg/essential/mixins/transformers/client/settings/Mixin_UnbindConflictingKeybinds_OnChange.java b/src/main/java/gg/essential/mixins/transformers/client/settings/Mixin_UnbindConflictingKeybinds_OnChange.java new file mode 100644 index 0000000..d943ceb --- /dev/null +++ b/src/main/java/gg/essential/mixins/transformers/client/settings/Mixin_UnbindConflictingKeybinds_OnChange.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.mixins.transformers.client.settings; + +import net.minecraft.client.settings.GameSettings; +import net.minecraft.client.settings.KeyBinding; +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; + +import static gg.essential.mixins.impl.client.settings.KeybindUtils.getKeyBindSaveToolbar; +import static gg.essential.mixins.impl.client.settings.KeybindUtils.getKeyBindZoom; +import static gg.essential.mixins.impl.client.settings.KeybindUtils.unbindIfConflicting; + +//#if MC >= 11600 +//$$ import net.minecraft.client.util.InputMappings; +//#endif + +//#if MC>=12102 +//$$ @Mixin(KeyBinding.class) +//#else +@Mixin(GameSettings.class) +//#endif +public class Mixin_UnbindConflictingKeybinds_OnChange { + //#if MC>=12102 + //$$ @Inject(method = "setBoundKey", at = @At("RETURN")) + //$$ private void Essential$unbindSetConflictingKeybinds(InputUtil.Key keyCode, CallbackInfo ci) { + //$$ KeyBinding key = (KeyBinding) (Object) this; + //#elseif MC >= 11600 + //$$ @Inject(method = "setKeyBindingCode", at = @At("RETURN")) + //$$ private void Essential$unbindSetConflictingKeybinds(KeyBinding key, InputMappings.Input keyCode, CallbackInfo ci) { + //#else + @Inject(method = "setOptionKeyBinding", at = @At("RETURN")) + private void Essential$unbindSetConflictingKeybinds(KeyBinding key, int keyCode, CallbackInfo ci) { + //#endif + // Unbind either save toolbar activator or zoom keybind if they conflict, whichever is unchanged + if (key == getKeyBindZoom()) { + unbindIfConflicting(key, getKeyBindSaveToolbar()); + } else if (key == getKeyBindSaveToolbar()) { + unbindIfConflicting(key, getKeyBindZoom()); + } + } +} diff --git a/src/main/java/gg/essential/mixins/transformers/client/settings/Mixin_UnbindConflictingKeybinds_OnLoad.java b/src/main/java/gg/essential/mixins/transformers/client/settings/Mixin_UnbindConflictingKeybinds_OnLoad.java new file mode 100644 index 0000000..95ec2c8 --- /dev/null +++ b/src/main/java/gg/essential/mixins/transformers/client/settings/Mixin_UnbindConflictingKeybinds_OnLoad.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.mixins.transformers.client.settings; + +import net.minecraft.client.settings.GameSettings; +import net.minecraft.client.settings.KeyBinding; +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; + +import static gg.essential.mixins.impl.client.settings.KeybindUtils.getKeyBindZoom; +import static gg.essential.mixins.impl.client.settings.KeybindUtils.unbindIfConflicting; + +@Mixin(GameSettings.class) +public class Mixin_UnbindConflictingKeybinds_OnLoad { + + @Shadow + private KeyBinding keyBindSaveToolbar; + + @Inject(method = "loadOptions", at = @At("RETURN")) + private void Essential$unbindLoadedConflictingKeybinds(CallbackInfo ci) { + // Unbind default save toolbar activator keybind if it conflicts with zoom keybind + unbindIfConflicting(getKeyBindZoom(), this.keyBindSaveToolbar); + } +} diff --git a/src/main/java/gg/essential/mixins/transformers/compatibility/emf/Mixin_EMFAnimationEntityContext.java b/src/main/java/gg/essential/mixins/transformers/compatibility/emf/Mixin_EMFAnimationEntityContext.java new file mode 100644 index 0000000..2e1a0a0 --- /dev/null +++ b/src/main/java/gg/essential/mixins/transformers/compatibility/emf/Mixin_EMFAnimationEntityContext.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.mixins.transformers.compatibility.emf; + +import gg.essential.mixins.DummyTarget; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(DummyTarget.class) +public class Mixin_EMFAnimationEntityContext { +} diff --git a/src/main/java/gg/essential/mixins/transformers/compatibility/labymod/MixinRenderPlayerCustom.java b/src/main/java/gg/essential/mixins/transformers/compatibility/labymod/MixinRenderPlayerCustom.java index 6f50f31..e7ac1c3 100644 --- a/src/main/java/gg/essential/mixins/transformers/compatibility/labymod/MixinRenderPlayerCustom.java +++ b/src/main/java/gg/essential/mixins/transformers/compatibility/labymod/MixinRenderPlayerCustom.java @@ -13,8 +13,6 @@ //#if MC<=11202 import gg.essential.mixins.transformers.client.renderer.entity.MixinRenderPlayer; -import net.minecraft.client.model.ModelBase; -import net.minecraft.client.renderer.entity.RenderManager; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Pseudo; import org.spongepowered.asm.mixin.injection.At; @@ -25,10 +23,6 @@ @Mixin(targets = "net.labymod.mojang.RenderPlayerHook$RenderPlayerCustom") @SuppressWarnings("UnresolvedMixinReference") public abstract class MixinRenderPlayerCustom extends MixinRenderPlayer { - public MixinRenderPlayerCustom(RenderManager renderManagerIn, ModelBase modelBaseIn, float shadowSizeIn) { - super(renderManagerIn, modelBaseIn, shadowSizeIn); - } - @Inject(method = "", at = @At("RETURN"), remap = false) private void initEssentialCosmeticsLayer(CallbackInfo ci) { layerRenderers.add(super.essentialModelRenderer); // Already initialized in the parent constructor diff --git a/src/main/java/gg/essential/mixins/transformers/compatibility/vanilla/Mixin_FixKeyboardNavigation.java b/src/main/java/gg/essential/mixins/transformers/compatibility/vanilla/Mixin_FixKeyboardNavigation.java new file mode 100644 index 0000000..8b7cf8f --- /dev/null +++ b/src/main/java/gg/essential/mixins/transformers/compatibility/vanilla/Mixin_FixKeyboardNavigation.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.mixins.transformers.compatibility.vanilla; + +import gg.essential.mixins.transformers.client.gui.ServerSelectionListAccessor; +import net.minecraft.client.gui.GuiMultiplayer; +import net.minecraft.client.gui.ServerSelectionList; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(GuiMultiplayer.class) +public class Mixin_FixKeyboardNavigation { + + // There is a mistake in the vanilla code that skips dividers, it calls getSize() instead of getSelected() + @Redirect(method = "keyTyped", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/ServerSelectionList;getSize()I", ordinal = 0)) + public int fixSkippingDividersUp(ServerSelectionList instance) { + return instance.getSelected(); + } + + // There is a mistake in the vanilla code that skips dividers, it calls getSize() instead of getSelected() + @Redirect(method = "keyTyped", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/ServerSelectionList;getSize()I", ordinal = 3)) + public int fixSkippingDividersDown(ServerSelectionList instance) { + return instance.getSelected(); + } + + // There is an off by one in the vanilla code, it should be `i < this.serverListSelector.getSize() - 1` + // not `i < this.serverListSelector.getSize()`. + @Redirect(method = "keyTyped", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/ServerSelectionList;getSize()I", ordinal = 1)) + public int fixOffByOne(ServerSelectionList instance) { + return ((ServerSelectionListAccessor) instance).invokeGetSize() - 1; + } +} diff --git a/src/main/java/gg/essential/mixins/transformers/cosmetics/capes/Mixin_RedirectVanillaCapeSetting.java b/src/main/java/gg/essential/mixins/transformers/cosmetics/capes/Mixin_RedirectVanillaCapeSetting.java index 133927a..563c7f9 100644 --- a/src/main/java/gg/essential/mixins/transformers/cosmetics/capes/Mixin_RedirectVanillaCapeSetting.java +++ b/src/main/java/gg/essential/mixins/transformers/cosmetics/capes/Mixin_RedirectVanillaCapeSetting.java @@ -28,7 +28,11 @@ @Mixin(GameSettings.class) public class Mixin_RedirectVanillaCapeSetting { //#if MC>=11700 + //#if MC>=12102 + //$$ @Inject(method = "setPlayerModelPart", at = @At("HEAD"), cancellable = true) + //#else //$$ @Inject(method = "togglePlayerModelPart", at = @At("HEAD"), cancellable = true) + //#endif //$$ private void redirectCapeChangesToOutfit(PlayerModelPart part, boolean shouldBeEnabled, CallbackInfo ci) { //#else @Inject(method = "switchModelPartEnabled", at = @At("HEAD"), cancellable = true) diff --git a/src/main/java/gg/essential/mixins/transformers/cosmetics/skinmask/Mixin_ApplyToPlayerRenderer.java b/src/main/java/gg/essential/mixins/transformers/cosmetics/skinmask/Mixin_ApplyToPlayerRenderer.java index 3109bff..475743e 100644 --- a/src/main/java/gg/essential/mixins/transformers/cosmetics/skinmask/Mixin_ApplyToPlayerRenderer.java +++ b/src/main/java/gg/essential/mixins/transformers/cosmetics/skinmask/Mixin_ApplyToPlayerRenderer.java @@ -20,10 +20,21 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +//#if MC>=12102 +//$$ import gg.essential.mixins.impl.client.model.PlayerEntityRenderStateExt; +//$$ import net.minecraft.client.render.entity.state.PlayerEntityRenderState; +//#endif + @Mixin(RenderPlayer.class) public abstract class Mixin_ApplyToPlayerRenderer { + //#if MC>=12102 + //$$ @Inject(method = "getTexture(Lnet/minecraft/client/render/entity/state/PlayerEntityRenderState;)Lnet/minecraft/util/Identifier;", at = @At("RETURN"), cancellable = true) + //$$ private void applyCosmeticsSkinMask(PlayerEntityRenderState state, CallbackInfoReturnable ci) { + //$$ AbstractClientPlayerEntity player = ((PlayerEntityRenderStateExt) state).essential$getEntity(); + //#else @Inject(method = "getEntityTexture(Lnet/minecraft/client/entity/AbstractClientPlayer;)Lnet/minecraft/util/ResourceLocation;", at = @At("RETURN"), cancellable = true) private void applyCosmeticsSkinMask(AbstractClientPlayer player, CallbackInfoReturnable ci) { + //#endif ci.setReturnValue(((AbstractClientPlayerExt) player).applyEssentialCosmeticsMask(ci.getReturnValue())); } } diff --git a/src/main/java/gg/essential/mixins/transformers/events/Mixin_GuiDrawScreenEvent_Priority_LoadingScreen.java b/src/main/java/gg/essential/mixins/transformers/events/Mixin_GuiDrawScreenEvent_Priority_LoadingScreen.java new file mode 100644 index 0000000..3b86cb1 --- /dev/null +++ b/src/main/java/gg/essential/mixins/transformers/events/Mixin_GuiDrawScreenEvent_Priority_LoadingScreen.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.mixins.transformers.events; + +import gg.essential.mixins.DummyTarget; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(DummyTarget.class) +public abstract class Mixin_GuiDrawScreenEvent_Priority_LoadingScreen { +} diff --git a/src/main/java/gg/essential/mixins/transformers/events/Mixin_RenderTickEvent.java b/src/main/java/gg/essential/mixins/transformers/events/Mixin_RenderTickEvent.java index 045ce22..c1e4112 100644 --- a/src/main/java/gg/essential/mixins/transformers/events/Mixin_RenderTickEvent.java +++ b/src/main/java/gg/essential/mixins/transformers/events/Mixin_RenderTickEvent.java @@ -65,7 +65,7 @@ private void fireTickEvent(boolean pre) { //$$ float partialTicksMenu = this.timer.renderPartialTicks; //$$ float partialTicksInGame = partialTicksMenu; //#endif - Essential.EVENT_BUS.post(new RenderTickEvent(pre, matrixStack, partialTicksMenu, partialTicksInGame)); + Essential.EVENT_BUS.post(new RenderTickEvent(pre, false, matrixStack, partialTicksMenu, partialTicksInGame)); } } diff --git a/src/main/java/gg/essential/mixins/transformers/events/Mixin_RenderTickEvent_LoadingScreen.java b/src/main/java/gg/essential/mixins/transformers/events/Mixin_RenderTickEvent_LoadingScreen.java new file mode 100644 index 0000000..346bd5e --- /dev/null +++ b/src/main/java/gg/essential/mixins/transformers/events/Mixin_RenderTickEvent_LoadingScreen.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +// Newer versions treat loading screens as regular screens and use the regular render method +//#if MC<11600 +package gg.essential.mixins.transformers.events; + +import gg.essential.Essential; +import gg.essential.event.render.RenderTickEvent; +import gg.essential.universal.UMatrixStack; +import net.minecraft.client.LoadingScreenRenderer; +import net.minecraft.client.Minecraft; +import org.spongepowered.asm.mixin.Final; +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(LoadingScreenRenderer.class) +public class Mixin_RenderTickEvent_LoadingScreen { + @Shadow @Final private Minecraft mc; + + @Inject(method = "setLoadingProgress", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/shader/Framebuffer;unbindFramebuffer()V")) + private void renderTickPost(CallbackInfo callbackInfo) { + //#if MC>=11200 + float partialTicks = this.mc.getRenderPartialTicks(); + //#else + //$$ float partialTicks = 0; // FIXME could get this via Accessor, but we don't really need it atm anyway + //#endif + Essential.EVENT_BUS.post(new RenderTickEvent(false, true, new UMatrixStack(), partialTicks, partialTicks)); + } +} +//#else +//$$ package gg.essential.mixins.transformers.events; +//$$ @org.spongepowered.asm.mixin.Mixin(gg.essential.mixins.DummyTarget.class) +//$$ public abstract class Mixin_RenderTickEvent_LoadingScreen {} +//#endif diff --git a/src/main/java/gg/essential/mixins/transformers/feature/cosmetics/Mixin_UpdateOffscreenPlayers.java b/src/main/java/gg/essential/mixins/transformers/feature/cosmetics/Mixin_UpdateOffscreenPlayers.java index ece959c..ad9e166 100644 --- a/src/main/java/gg/essential/mixins/transformers/feature/cosmetics/Mixin_UpdateOffscreenPlayers.java +++ b/src/main/java/gg/essential/mixins/transformers/feature/cosmetics/Mixin_UpdateOffscreenPlayers.java @@ -38,7 +38,10 @@ public abstract class Mixin_UpdateOffscreenPlayers { @Shadow private WorldClient world; - //#if MC>=11400 + // TODO 1.21.2 is this place still good? maybe separate update from render entirely + //#if MC>=12102 + //$$ @Inject(method = "method_62214", at = @At(value = "CONSTANT", args = "stringValue=blockentities")) + //#elseif MC>=11400 //$$ @Inject(method = "updateCameraAndRender", at = @At(value = "CONSTANT", args = "stringValue=blockentities")) //#else @Inject(method = "renderEntities", at = @At("RETURN")) diff --git a/src/main/java/gg/essential/mixins/transformers/feature/ice/client/Mixin_IceAddressResolving_Connect.java b/src/main/java/gg/essential/mixins/transformers/feature/ice/client/Mixin_IceAddressResolving_Connect.java index 188c283..4c4dca3 100644 --- a/src/main/java/gg/essential/mixins/transformers/feature/ice/client/Mixin_IceAddressResolving_Connect.java +++ b/src/main/java/gg/essential/mixins/transformers/feature/ice/client/Mixin_IceAddressResolving_Connect.java @@ -32,7 +32,7 @@ public abstract class Mixin_IceAddressResolving_Connect { //#if MC>=11700 //$$ @ModifyArg(method = "run", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/AllowedAddressResolver;resolve(Lnet/minecraft/client/network/ServerAddress;)Ljava/util/Optional;", remap = true), remap = false) - //$$ private ServerAddress setIceTarget(ServerAddress address) { + //$$ private ServerAddress setIceTarget(ServerAddress address) throws Exception { //$$ String host = setIceTarget(address.getAddress()); //$$ if (!host.equals(address.getAddress())) { //$$ address = new ServerAddress(host, address.getPort()); @@ -42,9 +42,11 @@ public abstract class Mixin_IceAddressResolving_Connect { //#else @ModifyArg(method = "run", at = @At(value = "INVOKE", target = "Ljava/net/InetAddress;getByName(Ljava/lang/String;)Ljava/net/InetAddress;"), remap = false) //#endif - private String setIceTarget(String address) { - SPSManager spsManager = Essential.getInstance().getConnectionManager().getSpsManager(); - UUID user = spsManager.getHostFromSpsAddress(address); + private String setIceTarget(String address) throws Exception { + Essential essential = Essential.getInstance(); + UUID user; + SPSManager spsManager = essential.getConnectionManager().getSpsManager(); + user = spsManager.getHostFromSpsAddress(address); if (user != null) { connectTarget.set(user); address = "127.0.0.1"; // just needs to resolve diff --git a/src/main/java/gg/essential/mixins/transformers/feature/particles/Mixin_RenderParticleSystemOfClientWorld.java b/src/main/java/gg/essential/mixins/transformers/feature/particles/Mixin_RenderParticleSystemOfClientWorld.java index 5101fce..eb07351 100644 --- a/src/main/java/gg/essential/mixins/transformers/feature/particles/Mixin_RenderParticleSystemOfClientWorld.java +++ b/src/main/java/gg/essential/mixins/transformers/feature/particles/Mixin_RenderParticleSystemOfClientWorld.java @@ -26,12 +26,14 @@ import org.spongepowered.asm.mixin.injection.Group; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import java.util.UUID; import static dev.folomeev.kotgl.matrix.matrices.Matrices.identityMat3; import static dev.folomeev.kotgl.matrix.matrices.Matrices.identityMat4; import static dev.folomeev.kotgl.matrix.vectors.Vectors.vec3; import static dev.folomeev.kotgl.matrix.vectors.Vectors.vecUnitY; import static dev.folomeev.kotgl.matrix.vectors.mutables.MutableVectors.times; +import static gg.essential.util.HelpersKt.getPerspective; //#if MC>=11600 //$$ import com.mojang.blaze3d.matrix.MatrixStack; @@ -114,7 +116,9 @@ public abstract class Mixin_RenderParticleSystemOfClientWorld { return; } - //#if MC>=11600 + //#if MC>=12102 + //$$ net.minecraft.util.profiler.Profiler profiler = net.minecraft.util.profiler.Profilers.get(); + //#elseif MC>=11600 //$$ net.minecraft.profiler.IProfiler profiler = this.world.getProfiler(); //#else net.minecraft.profiler.Profiler profiler = this.world.profiler; @@ -170,7 +174,14 @@ public abstract class Mixin_RenderParticleSystemOfClientWorld { stack.translate((float) -Particle.interpPosX, (float) -Particle.interpPosY, (float) -Particle.interpPosZ); //#endif - particleSystem.render(stack, cameraPos, cameraRot, new MinecraftRenderBackend.ParticleVertexConsumerProvider()); + //#if MC>=11600 + //$$ UUID cameraUuid = activeRenderInfoIn.getRenderViewEntity().getUniqueID(); + //#else + UUID cameraUuid = cameraEntity.getUniqueID(); + //#endif + + boolean isFirstPerson = getPerspective() == 0; + particleSystem.render(stack, cameraPos, cameraRot, new MinecraftRenderBackend.ParticleVertexConsumerProvider(), cameraUuid, isFirstPerson); profiler.endSection(); } diff --git a/src/main/java/gg/essential/mixins/transformers/feature/sps/Mixin_PlayerJoinSessionEvent.java b/src/main/java/gg/essential/mixins/transformers/feature/sps/Mixin_PlayerJoinSessionEvent.java index 45b6516..e053d06 100644 --- a/src/main/java/gg/essential/mixins/transformers/feature/sps/Mixin_PlayerJoinSessionEvent.java +++ b/src/main/java/gg/essential/mixins/transformers/feature/sps/Mixin_PlayerJoinSessionEvent.java @@ -14,17 +14,23 @@ import com.mojang.authlib.GameProfile; import gg.essential.Essential; import gg.essential.event.sps.PlayerJoinSessionEvent; +import gg.essential.mixins.ext.server.integrated.IntegratedServerExt; import gg.essential.network.connectionmanager.sps.SPSManager; import gg.essential.util.ExtensionsKt; import net.minecraft.client.Minecraft; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.network.NetworkManager; +import net.minecraft.server.MinecraftServer; import net.minecraft.server.management.PlayerList; 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; +import java.util.UUID; + +import static gg.essential.gui.elementa.state.v2.ListKt.add; + //#if MC>=12002 //$$ import net.minecraft.server.network.ConnectedClientData; //#endif @@ -52,6 +58,15 @@ public class Mixin_PlayerJoinSessionEvent { NetHandlerPlayServer nethandlerplayserver, //#endif CallbackInfo info) { + + MinecraftServer server = player.mcServer; + UUID uuid = player.getUniqueID(); + ExtensionsKt.getExecutor(Minecraft.getMinecraft()).execute(() -> { + if (server instanceof IntegratedServerExt) { + add(((IntegratedServerExt) server).getEssential$manager().getConnectedPlayers(), uuid); + } + }); + final SPSManager spsManager = Essential.getInstance().getConnectionManager().getSpsManager(); if (spsManager.getLocalSession() != null) { GameProfile gameProfile = player.getGameProfile(); diff --git a/src/main/java/gg/essential/mixins/transformers/feature/sps/Mixin_PlayerLeaveSessionEvent.java b/src/main/java/gg/essential/mixins/transformers/feature/sps/Mixin_PlayerLeaveSessionEvent.java index 2f671a4..07ca454 100644 --- a/src/main/java/gg/essential/mixins/transformers/feature/sps/Mixin_PlayerLeaveSessionEvent.java +++ b/src/main/java/gg/essential/mixins/transformers/feature/sps/Mixin_PlayerLeaveSessionEvent.java @@ -14,21 +14,35 @@ import com.mojang.authlib.GameProfile; import gg.essential.Essential; import gg.essential.event.sps.PlayerLeaveSessionEvent; +import gg.essential.mixins.ext.server.integrated.IntegratedServerExt; import gg.essential.network.connectionmanager.sps.SPSManager; import gg.essential.util.ExtensionsKt; import net.minecraft.client.Minecraft; import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.server.MinecraftServer; import net.minecraft.server.management.PlayerList; 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; +import java.util.UUID; + +import static gg.essential.gui.elementa.state.v2.ListKt.remove; + @Mixin(PlayerList.class) public class Mixin_PlayerLeaveSessionEvent { @Inject(method = "playerLoggedOut", at = @At("RETURN")) private void essential$playerLeaveSession(EntityPlayerMP player, CallbackInfo info) { + MinecraftServer server = player.mcServer; + UUID uuid = player.getUniqueID(); + ExtensionsKt.getExecutor(Minecraft.getMinecraft()).execute(() -> { + if (server instanceof IntegratedServerExt) { + remove(((IntegratedServerExt) server).getEssential$manager().getConnectedPlayers(), uuid); + } + }); + final SPSManager spsManager = Essential.getInstance().getConnectionManager().getSpsManager(); if (spsManager.getLocalSession() != null) { GameProfile gameProfile = player.getGameProfile(); diff --git a/src/main/java/gg/essential/mixins/transformers/item/MixinElytraItem.java b/src/main/java/gg/essential/mixins/transformers/item/MixinElytraItem.java index 998ffb2..48c455f 100644 --- a/src/main/java/gg/essential/mixins/transformers/item/MixinElytraItem.java +++ b/src/main/java/gg/essential/mixins/transformers/item/MixinElytraItem.java @@ -11,13 +11,15 @@ */ package gg.essential.mixins.transformers.item; -//#if MC > 10900 +//#if MC > 10900 && MC<12102 import net.minecraft.item.ItemElytra; //#endif import gg.essential.api.cosmetics.RenderCosmetic; import org.spongepowered.asm.mixin.Mixin; -//#if MC > 10900 +// Elytra doesn't exist in 1.8 +// ElytraItem class no longer exists in 1.21.2, now handled in PlayerWearableManager.canRenderCosmetic +//#if MC > 10900 && MC<12102 @Mixin(ItemElytra.class) //#else //$$ @Mixin(gg.essential.mixins.DummyTarget.class) diff --git a/src/main/java/gg/essential/mixins/transformers/server/Mixin_PublishServerStatusResponse.java b/src/main/java/gg/essential/mixins/transformers/server/Mixin_PublishServerStatusResponse.java index 3d22331..42000ac 100644 --- a/src/main/java/gg/essential/mixins/transformers/server/Mixin_PublishServerStatusResponse.java +++ b/src/main/java/gg/essential/mixins/transformers/server/Mixin_PublishServerStatusResponse.java @@ -12,7 +12,9 @@ package gg.essential.mixins.transformers.server; import gg.essential.Essential; +import gg.essential.mixins.ext.server.integrated.IntegratedServerExt; import gg.essential.network.connectionmanager.sps.SPSManager; +import gg.essential.sps.McIntegratedServerManager; import io.netty.buffer.Unpooled; import net.minecraft.network.PacketBuffer; import net.minecraft.network.ServerStatusResponse; @@ -50,6 +52,8 @@ public class Mixin_PublishServerStatusResponse { ) ) private void publishUpdatedStatus(CallbackInfo ci) { + McIntegratedServerManager manager = + this instanceof IntegratedServerExt ? ((IntegratedServerExt) this).getEssential$manager() : null; SPSManager spsManager = Essential.getInstance().getConnectionManager().getSpsManager(); if (spsManager.getLocalSession() != null) { // Not using .getJson() directly cause that's Forge-only diff --git a/src/main/java/gg/essential/mixins/transformers/server/Mixin_ServerCoroutineScope.java b/src/main/java/gg/essential/mixins/transformers/server/Mixin_ServerCoroutineScope.java new file mode 100644 index 0000000..2676da2 --- /dev/null +++ b/src/main/java/gg/essential/mixins/transformers/server/Mixin_ServerCoroutineScope.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.mixins.transformers.server; + +import gg.essential.mixins.ext.server.MinecraftServerExt; +import gg.essential.util.SingleThreadDispatcher; +import kotlinx.coroutines.CoroutineDispatcher; +import kotlinx.coroutines.CoroutineScope; +import net.minecraft.server.MinecraftServer; +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; + +import static kotlinx.coroutines.CoroutineScopeKt.CoroutineScope; +import static kotlinx.coroutines.CoroutineScopeKt.cancel; +import static kotlinx.coroutines.SupervisorKt.SupervisorJob; + +@Mixin(MinecraftServer.class) +public abstract class Mixin_ServerCoroutineScope implements MinecraftServerExt { + @Unique + private SingleThreadDispatcher dispatcher; + + @Unique + private CoroutineScope coroutineScope; + + //#if MC>=11200 + @Inject(method = "", at = @At("RETURN")) + //#else + //$$ @Inject(method = "(Ljava/io/File;Ljava/net/Proxy;Ljava/io/File;)V", at = @At("RETURN")) + //#endif + private void init(CallbackInfo ci) { + // FIXME mixin doesn't emit the first of these two lines if I make them field initializers, why? + dispatcher = new SingleThreadDispatcher("MinecraftServer.dispatcher"); + coroutineScope = CoroutineScope(SupervisorJob(null).plus(dispatcher)); + } + + @Inject(method = "tick", at = @At("HEAD")) + protected void essential$runTasks(CallbackInfo ci) { + dispatcher.runTasks(); + } + + @Inject(method = "stopServer", at = @At("HEAD")) + private void cancelCoroutineScope(CallbackInfo ci) { + cancel(coroutineScope, null); + + dispatcher.runTasks(); + } + + @Inject(method = "stopServer", at = @At("RETURN")) + private void shutdownDispatcher(CallbackInfo ci) { + dispatcher.shutdown(); + } + + @NotNull + @Override + public CoroutineDispatcher getEssential$dispatcher() { + return dispatcher; + } + + @NotNull + @Override + public CoroutineScope getEssential$coroutineScope() { + return coroutineScope; + } +} diff --git a/src/main/java/gg/essential/mixins/transformers/server/Mixin_ServerCoroutineScope_IntegratedServer.java b/src/main/java/gg/essential/mixins/transformers/server/Mixin_ServerCoroutineScope_IntegratedServer.java new file mode 100644 index 0000000..4d7d1b0 --- /dev/null +++ b/src/main/java/gg/essential/mixins/transformers/server/Mixin_ServerCoroutineScope_IntegratedServer.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.mixins.transformers.server; + +import net.minecraft.server.integrated.IntegratedServer; +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(IntegratedServer.class) +public abstract class Mixin_ServerCoroutineScope_IntegratedServer extends Mixin_ServerCoroutineScope { + // The integrated server doesn't run `super.tick` while the game is paused, we do want to be able to schedule + // stuff to run on the server thread even while the game is paused though, so we'll have to call our runTasks method + // from here as well (this does mean it'll be called twice while not paused, but there's nothing wrong with that). + @Inject(method = "tick", at = @At("HEAD")) + protected void runTasks(CallbackInfo ci) { + essential$runTasks(ci); + } +} diff --git a/src/main/java/gg/essential/mixins/transformers/server/integrated/Mixin_IntegratedServerManager.java b/src/main/java/gg/essential/mixins/transformers/server/integrated/Mixin_IntegratedServerManager.java new file mode 100644 index 0000000..f75809c --- /dev/null +++ b/src/main/java/gg/essential/mixins/transformers/server/integrated/Mixin_IntegratedServerManager.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.mixins.transformers.server.integrated; + +import gg.essential.mixins.ext.server.integrated.IntegratedServerExt; +import gg.essential.sps.McIntegratedServerManager; +import net.minecraft.server.integrated.IntegratedServer; +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; + +@Mixin(IntegratedServer.class) +public abstract class Mixin_IntegratedServerManager implements IntegratedServerExt { + @Unique + private McIntegratedServerManager manager; + + // Note: Needs to be initialized after `this.mc` is set. + //#if MC>=11200 + @Inject(method = "", at = @At("RETURN")) + //#else + //$$ // 1.8.9 in its Minecraft constructor for some reason creates an IntegratedServer without any folder. + //$$ // It doesn't appear to use it for anything and it's gone it 1.12.2, so we'll just skip creating our manager + //$$ // in that case by targeting the regular constructor only. + //$$ @Inject(method = "(Lnet/minecraft/client/Minecraft;Ljava/lang/String;Ljava/lang/String;Lnet/minecraft/world/WorldSettings;)V", at = @At("RETURN")) + //#endif + private void initIntegratedServerManager(CallbackInfo ci) { + manager = new McIntegratedServerManager((IntegratedServer) (Object) this); + } + + @NotNull + @Override + public McIntegratedServerManager getEssential$manager() { + return manager; + } +} diff --git a/src/main/java/gg/essential/model/PlayerMolangQuery.java b/src/main/java/gg/essential/model/PlayerMolangQuery.java index 10b817f..2711a36 100644 --- a/src/main/java/gg/essential/model/PlayerMolangQuery.java +++ b/src/main/java/gg/essential/model/PlayerMolangQuery.java @@ -81,18 +81,68 @@ public float getTime() { } } + //#if MC>=12102 + //$$ // MC no longer tracks the prevDistanceTraveled, so we now do that ourselves. + //$$ // The value will only be updated while someone is observing it, which should be good enough though assuming the + //$$ // same PlayerMolangQuery is being reused across time (which is the case). It'll sill be slightly wrong on the + //$$ // first tick that an emote/cosmetic which cares about this value is equipped, but I doubt that's even noticeable. + //$$ private float prevDistanceTraveled; + //$$ private float latestDistanceTraveled; + //$$ private int latestDistanceTraveledAge; + //$$ private void updateDistanceTraveled() { + //$$ int age = player.age; + //$$ + //$$ // If we haven't updated in a while, reset everything to the current values + //$$ if (age > latestDistanceTraveledAge + 10) { + //$$ latestDistanceTraveledAge = age; + //$$ prevDistanceTraveled = latestDistanceTraveled = player.distanceTraveled; + //$$ return; + //$$ } + //$$ + //$$ // If this is the first update call this tick + //$$ int ticksSinceLastUpdate = age - latestDistanceTraveledAge; + //$$ if (ticksSinceLastUpdate > 0) { + //$$ // compute the value of the previous tick + //$$ if (ticksSinceLastUpdate == 1) { + //$$ // trivial case, stored value is the value of the previous tick + //$$ prevDistanceTraveled = latestDistanceTraveled; + //$$ } else { + //$$ // multiple ticks have happened, assume movement to be uniform across them + //$$ float movedSinceLastUpdate = player.distanceTraveled - latestDistanceTraveled; + //$$ prevDistanceTraveled = player.distanceTraveled - movedSinceLastUpdate / ticksSinceLastUpdate; + //$$ } + //$$ + //$$ // and store the latest value again + //$$ latestDistanceTraveled = player.distanceTraveled; + //$$ latestDistanceTraveledAge = age; + //$$ } + //$$ } + //#endif + @Override public float getModifiedDistanceMoved() { + //#if MC>=12102 + //$$ updateDistanceTraveled(); + //$$ float next = player.distanceTraveled * 0.6f; + //$$ float prev = prevDistanceTraveled * 0.6f; + //#else float next = player.distanceWalkedModified; float prev = player.prevDistanceWalkedModified; + //#endif float now = prev + (next - prev) * getPartialTicks(); return now * 16; // unclear what the unit is supposed to be } @Override public float getModifiedMoveSpeed() { + //#if MC>=12102 + //$$ updateDistanceTraveled(); + //$$ float next = player.distanceTraveled * 0.6f; + //$$ float prev = prevDistanceTraveled * 0.6f; + //#else float next = player.distanceWalkedModified; float prev = player.prevDistanceWalkedModified; + //#endif return (next - prev) * 16; // unclear what the unit is supposed to be } diff --git a/src/main/java/gg/essential/network/connectionmanager/ConnectionCodec.java b/src/main/java/gg/essential/network/connectionmanager/ConnectionCodec.java index 4428dd4..f07cc07 100644 --- a/src/main/java/gg/essential/network/connectionmanager/ConnectionCodec.java +++ b/src/main/java/gg/essential/network/connectionmanager/ConnectionCodec.java @@ -100,7 +100,7 @@ public Packet decode(byte[] array) { final String jsonString = this.readString(dataInputStream); if (LOG_PACKETS) { - Essential.debug.info(packetId + " - " + packetClass.getSimpleName() + " " + jsonString); + Essential.debug.info("IN " + packetId + " - " + packetName + " " + jsonString); } try { packet = gson.fromJson(jsonString, packetClass); @@ -145,7 +145,7 @@ public void encode(Packet packet, Consumer send) { packetIdBytes = (packetId != null ? packetId.toString().getBytes(StandardCharsets.UTF_8) : EMPTY_BYTE_ARRAY); if (LOG_PACKETS) { - Essential.debug.info(packetId + " - " + packet.getClass().getSimpleName() + " " + new String(packetBytes)); + Essential.debug.info("OUT " + packetId + " - " + splitPacketPackage(packet.getClass()) + " " + new String(packetBytes)); } try ( final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); diff --git a/src/main/java/gg/essential/network/connectionmanager/ConnectionManager.java b/src/main/java/gg/essential/network/connectionmanager/ConnectionManager.java index aae3d78..7dc1d67 100644 --- a/src/main/java/gg/essential/network/connectionmanager/ConnectionManager.java +++ b/src/main/java/gg/essential/network/connectionmanager/ConnectionManager.java @@ -16,10 +16,12 @@ import gg.essential.connectionmanager.common.packet.multiplayer.ServerMultiplayerJoinServerPacket; import gg.essential.connectionmanager.common.packet.relationships.ServerUuidNameMapPacket; import gg.essential.event.client.PostInitializationEvent; +import gg.essential.gui.elementa.state.v2.State; import gg.essential.network.client.MinecraftHook; import gg.essential.network.connectionmanager.chat.ChatManager; import gg.essential.network.connectionmanager.coins.CoinsManager; import gg.essential.network.connectionmanager.cosmetics.CosmeticsManager; +import gg.essential.network.connectionmanager.cosmetics.EmoteWheelManager; import gg.essential.network.connectionmanager.cosmetics.OutfitManager; import gg.essential.network.connectionmanager.cosmetics.PacketHandlers; import gg.essential.network.connectionmanager.handler.PacketHandler; @@ -39,11 +41,13 @@ import gg.essential.network.connectionmanager.sps.SPSManager; import gg.essential.network.connectionmanager.subscription.SubscriptionManager; import gg.essential.network.connectionmanager.telemetry.TelemetryManager; +import gg.essential.sps.McIntegratedServerManager; import gg.essential.util.ModLoaderUtil; import gg.essential.util.Multithreading; import gg.essential.util.lwjgl3.Lwjgl3Loader; import kotlin.Unit; import kotlin.collections.MapsKt; +import kotlin.jvm.functions.Function0; import kotlin.jvm.functions.Function1; import kotlinx.coroutines.JobKt; import me.kbrewster.eventbus.Subscribe; @@ -96,6 +100,8 @@ public class ConnectionManager extends ConnectionManagerKt { private /*final*/ SkinsManager skinsManager; @NotNull private final OutfitManager outfitManager; + @NotNull + private final EmoteWheelManager emoteWheelManager; private boolean modsLoaded = false; private boolean modsSent = false; @@ -113,7 +119,12 @@ public enum Status { SUCCESS, } - public ConnectionManager(@NotNull final MinecraftHook minecraftHook, File baseDir, Lwjgl3Loader lwjgl3) { + public ConnectionManager( + @NotNull final MinecraftHook minecraftHook, + File baseDir, + Lwjgl3Loader lwjgl3, + State<@Nullable McIntegratedServerManager> integratedServerManager + ) { this.minecraftHook = minecraftHook; this.subscriptionManager = new SubscriptionManager(this); this.managers.add(this.subscriptionManager); @@ -160,7 +171,22 @@ public ConnectionManager(@NotNull final MinecraftHook minecraftHook, File baseDi this.managers.add(this.socialManager = new SocialManager(this)); // Ice - this.iceManager = new IceManagerMcImpl(this, baseDir.toPath(), uuid -> this.spsManager.getInvitedUsers().contains(uuid)); + this.iceManager = new IceManagerMcImpl( + this, + baseDir.toPath(), + integratedServerManager, + uuid -> { + if (this.spsManager.getInvitedUsers().contains(uuid)) { + return true; + } + McIntegratedServerManager server = integratedServerManager.getUntracked(); + if (server != null) { + Set whitelist = server.getWhitelist().getUntracked(); + return whitelist != null && whitelist.contains(uuid); + } + return false; + } + ); //Screenshots this.managers.add(this.screenshotManager = new ScreenshotManager(this, baseDir, lwjgl3)); @@ -178,6 +204,9 @@ public ConnectionManager(@NotNull final MinecraftHook minecraftHook, File baseDi this.outfitManager = new OutfitManager(this, this.cosmeticsManager, map(this.skinsManager.getSkins(), map -> MapsKt.mapValues(map, it -> it.getValue().getSkin()))); this.managers.add(this.outfitManager); + // Emote Wheels + this.managers.add(this.emoteWheelManager = new EmoteWheelManager(this, this.cosmeticsManager)); + } @NotNull @@ -255,6 +284,12 @@ public OutfitManager getOutfitManager() { return this.outfitManager; } + @NotNull + public EmoteWheelManager getEmoteWheelManager() { + return this.emoteWheelManager; + } + + @Override public boolean isOpen() { Connection connection = this.connection; return connection != null && connection.isOpen(); @@ -264,6 +299,16 @@ public boolean isAuthenticated() { return this.connection != null; } + @Override + public void registerOnConnected(@NotNull Function0 onConnected) { + this.managers.add(new NetworkedManager() { + @Override + public void onConnected() { + onConnected.invoke(); + } + }); + } + public void registerPacketHandler(Class cls, PacketHandler handler) { this.packetHandlers.register(cls, handler); } diff --git a/src/main/java/gg/essential/network/connectionmanager/chat/ChatManager.java b/src/main/java/gg/essential/network/connectionmanager/chat/ChatManager.java index 48f299a..10e93bd 100644 --- a/src/main/java/gg/essential/network/connectionmanager/chat/ChatManager.java +++ b/src/main/java/gg/essential/network/connectionmanager/chat/ChatManager.java @@ -30,6 +30,7 @@ import gg.essential.gui.elementa.state.v2.collections.MutableTrackedList; import gg.essential.gui.friends.message.MessageUtils; import gg.essential.gui.friends.message.v2.ClientMessage; +import gg.essential.gui.friends.message.v2.ClientMessageKt; import gg.essential.gui.friends.message.v2.MessageRef; import gg.essential.gui.friends.state.IMessengerManager; import gg.essential.gui.notification.ExtensionsKt; @@ -733,7 +734,7 @@ public void retrieveChannelHistoryUntil(MessageRef ref) { Message messageById = getMessageById(ref.getMessageId()); if (messageById != null) { - ref.supplyValue(ClientMessage.Companion.fromNetwork(messageById)); + ref.supplyValue(ClientMessageKt.infraInstanceToClient(messageById)); return; } @@ -784,7 +785,7 @@ public void messageReceived(Message message) { List messageRefs = messageRefMap.remove(message.getId()); if (messageRefs != null) { for (MessageRef messageRef : messageRefs) { - messageRef.supplyValue(ClientMessage.Companion.fromNetwork(message)); + messageRef.supplyValue(ClientMessageKt.infraInstanceToClient(message)); } } } diff --git a/src/main/java/gg/essential/network/connectionmanager/cosmetics/CosmeticsManager.java b/src/main/java/gg/essential/network/connectionmanager/cosmetics/CosmeticsManager.java index ce65ef3..b597b2c 100644 --- a/src/main/java/gg/essential/network/connectionmanager/cosmetics/CosmeticsManager.java +++ b/src/main/java/gg/essential/network/connectionmanager/cosmetics/CosmeticsManager.java @@ -19,9 +19,6 @@ import gg.essential.connectionmanager.common.packet.checkout.ClientCheckoutCosmeticsPacket; import gg.essential.connectionmanager.common.packet.cosmetic.*; import gg.essential.connectionmanager.common.packet.cosmetic.categories.ServerCosmeticCategoriesPopulatePacket; -import gg.essential.connectionmanager.common.packet.cosmetic.emote.ClientCosmeticEmoteWheelSelectPacket; -import gg.essential.connectionmanager.common.packet.cosmetic.emote.ClientCosmeticEmoteWheelUpdatePacket; -import gg.essential.connectionmanager.common.packet.cosmetic.emote.ServerCosmeticEmoteWheelPopulatePacket; import gg.essential.connectionmanager.common.packet.response.ResponseActionPacket; import gg.essential.cosmetics.EquippedCosmetic; import gg.essential.cosmetics.model.CosmeticUnlockData; @@ -38,7 +35,6 @@ import gg.essential.gui.elementa.state.v2.State; import gg.essential.gui.elementa.state.v2.StateKt; import gg.essential.gui.elementa.state.v2.collections.TrackedList; -import gg.essential.gui.emotes.EmoteWheel; import gg.essential.gui.modals.TOSModal; import gg.essential.gui.notification.Notifications; import gg.essential.mod.EssentialAsset; @@ -50,16 +46,13 @@ import gg.essential.network.connectionmanager.queue.PacketQueue; import gg.essential.network.connectionmanager.queue.SequentialPacketQueue; import gg.essential.network.connectionmanager.sps.SPSManager; -import gg.essential.network.cosmetics.ConversionsKt; import gg.essential.network.cosmetics.Cosmetic; import gg.essential.network.cosmetics.cape.CapeCosmeticsManager; import gg.essential.universal.UMinecraft; import gg.essential.util.GuiUtil; -import gg.essential.util.Multithreading; import kotlin.Pair; import kotlin.Unit; import kotlin.collections.MapsKt; -import kotlin.collections.CollectionsKt; import me.kbrewster.eventbus.Subscribe; import net.minecraft.client.Minecraft; import net.minecraft.client.network.NetHandlerPlayClient; @@ -71,7 +64,6 @@ import java.nio.file.Path; import java.util.*; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import static gg.essential.gui.elementa.state.v2.combinators.StateKt.map; @@ -103,8 +95,6 @@ public class CosmeticsManager implements NetworkedManager { @Nullable private final CosmeticsDataWithChanges cosmeticsDataWithChanges; @NotNull - private final Set emoteWheels = new TreeSet<>(Comparator.comparingLong(page -> page.getCreatedAt().toEpochMilli())); - @NotNull private final EquippedCosmeticsManager equippedCosmeticsManager; @NotNull private final MutableState> unlockedCosmeticsData = StateKt.mutableStateOf(new HashMap<>()); @@ -122,9 +112,6 @@ public class CosmeticsManager implements NetworkedManager { // If we've warned the user about cosmetics not working on offline mode servers private boolean shownOfflineModeWarning = false; - private int currentFlushEmoteWheelTaskId = 0; - private String sentEmoteWheelId = null; - public CosmeticsManager(@NotNull final ConnectionManager connectionManager, File baseDir) { this.connectionManager = connectionManager; @@ -175,7 +162,6 @@ public CosmeticsManager(@NotNull final ConnectionManager connectionManager, File connectionManager.registerPacketHandler(ServerCosmeticsUserEquippedVisibilityPacket.class, new ServerCosmeticsUserEquippedVisibilityPacketHandler()); connectionManager.registerPacketHandler(ServerCosmeticsSkinTexturePacket.class, new ServerCosmeticSkinTexturePacketHandler()); connectionManager.registerPacketHandler(ServerCosmeticCategoriesPopulatePacket.class, new ServerCosmeticCategoriesPopulatePacketHandler(this)); - connectionManager.registerPacketHandler(ServerCosmeticEmoteWheelPopulatePacket.class, new ServerCosmeticEmoteWheelPopulatePacketHandler()); connectionManager.registerPacketHandler(ServerWardrobeSettingsPacket.class, new ServerWardrobeSettingsPacketHandler()); connectionManager.registerPacketHandler(ServerWardrobeStoreBundlePacket.class, new ServerWardrobeStoreBundlePacketHandler()); @@ -282,8 +268,6 @@ public void clearUnlockedCosmetics(boolean allowAutoUnlockIfSideloading) { @Override public void resetState() { this.updateQueue.reset(); - this.emoteWheels.clear(); - this.sentEmoteWheelId = null; this.clearUnlockedCosmetics(true); this.infraCosmeticsData.resetState(); this.cosmeticsLoadedFuture = new CompletableFuture<>(); @@ -296,80 +280,6 @@ public void onConnected() { resetState(); } - @NotNull - public Set getEmoteWheels() { - return this.emoteWheels; - } - - @Nullable - public EmoteWheelPage getSelectedEmoteWheel() { - return this.emoteWheels.stream().filter(EmoteWheelPage::isSelected).findFirst().orElse(null); - } - - public void selectEmoteWheel(int index) { - if (index > this.emoteWheels.size() - 1) return; - - this.emoteWheels.stream().filter(EmoteWheelPage::isSelected) - .forEach(emoteWheelPage -> emoteWheelPage.setSelected(false)); - - EmoteWheelPage emoteWheel = CollectionsKt.elementAt(this.emoteWheels, index); - emoteWheel.setSelected(true); - - flushSelectedEmoteWheel(true); - } - - /** - * Selects the emote wheel at a given offset from the currently selected emote wheel, wrapping around if necessary. - * - * @return The index of the newly selected emote wheel - */ - public int shiftSelectedEmoteWheel(int offset) { - int numWheels = this.emoteWheels.size(); - int curIndex = CollectionsKt.indexOfFirst(this.emoteWheels, EmoteWheelPage::isSelected); - int newIndex = (curIndex + offset + numWheels) % numWheels; - selectEmoteWheel(newIndex); - return newIndex; - } - - public void populateEmoteWheels(List emoteWheels) { - for (gg.essential.cosmetics.model.EmoteWheel emoteWheel : emoteWheels) { - this.emoteWheels.add(ConversionsKt.toMod(emoteWheel)); - if (emoteWheel.selected()) { - sentEmoteWheelId = emoteWheel.id(); - } - } - - if (getSelectedEmoteWheel() == null) { - Essential.logger.error("No emote wheel was selected, selecting the first one."); - selectEmoteWheel(0); - } - } - - public void flushSelectedEmoteWheel(boolean debounce) { - int taskId = ++currentFlushEmoteWheelTaskId; - if (debounce) { - Multithreading.scheduleOnMainThread(() -> { - if (taskId != currentFlushEmoteWheelTaskId) { - return; - } - flushSelectedEmoteWheel(false); - }, 3, TimeUnit.SECONDS); - return; - } - - EmoteWheelPage selectedEmoteWheel = getSelectedEmoteWheel(); - if (selectedEmoteWheel == null) { - return; - } - String selectedEmoteWheelId = selectedEmoteWheel.getId(); - if (selectedEmoteWheelId.equals(sentEmoteWheelId)) { - return; - } - sentEmoteWheelId = selectedEmoteWheelId; - this.connectionManager.send(new ClientCosmeticEmoteWheelSelectPacket(selectedEmoteWheelId)); - } - - @Subscribe public void onSpsJoin(PlayerJoinSessionEvent event) { // Unlock SPS cosmetics if players join an SPS session that the user is hosting @@ -449,49 +359,6 @@ public void setOwnCosmeticVisibility(boolean notification, final boolean visible } } - /** - * Sets the saved emotes for the emote wheel - * - * @param emotes The (String) list of emotes to save - */ - public void setSavedEmotes(List<@Nullable String> emotes) { - EmoteWheelPage selectedEmoteWheel = getSelectedEmoteWheel(); - if (selectedEmoteWheel == null) { - return; - } - List slots = selectedEmoteWheel.getSlots(); - - for (int i = 0; i < emotes.size(); i++) { - String value = emotes.get(i); - - if (value != null && !getUnlockedCosmetics().get().contains(value)) { - continue; - } - - String previous = slots.set(i, value); - if (!Objects.equals(value, previous)) { - connectionManager.send(new ClientCosmeticEmoteWheelUpdatePacket(selectedEmoteWheel.getId(), i, value)); - } - } - } - - /** - * Get the list of saved emotes for the emote wheel - * - * @return List of saved emotes - */ - public List<@Nullable String> getSavedEmotes() { - EmoteWheelPage selectedEmoteWheel = getSelectedEmoteWheel(); - if (selectedEmoteWheel == null) { - return new ArrayList(EmoteWheel.SLOTS) {{ - for (int i = 0; i < EmoteWheel.SLOTS; i++) { - add(null); - } - }}; - } - return new ArrayList<>(selectedEmoteWheel.getSlots()); - } - public CompletableFuture claimFreeItems(@NotNull Set ids) { CompletableFuture future = new CompletableFuture<>(); connectionManager.send(new ClientCheckoutCosmeticsPacket(ids, null), it -> { diff --git a/src/main/java/gg/essential/network/connectionmanager/handler/cosmetics/ServerCosmeticEmoteWheelPopulatePacketHandler.java b/src/main/java/gg/essential/network/connectionmanager/handler/cosmetics/ServerCosmeticEmoteWheelPopulatePacketHandler.java deleted file mode 100644 index d617bd9..0000000 --- a/src/main/java/gg/essential/network/connectionmanager/handler/cosmetics/ServerCosmeticEmoteWheelPopulatePacketHandler.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.network.connectionmanager.handler.cosmetics; - -import gg.essential.connectionmanager.common.packet.cosmetic.emote.ServerCosmeticEmoteWheelPopulatePacket; -import gg.essential.network.connectionmanager.ConnectionManager; -import gg.essential.network.connectionmanager.handler.PacketHandler; -import org.jetbrains.annotations.NotNull; - -public class ServerCosmeticEmoteWheelPopulatePacketHandler extends PacketHandler { - - @Override - protected void onHandle(@NotNull ConnectionManager connectionManager, @NotNull ServerCosmeticEmoteWheelPopulatePacket packet) { - connectionManager.getCosmeticsManager().populateEmoteWheels(packet.emoteWheels()); - } -} diff --git a/src/main/java/gg/essential/network/connectionmanager/handler/skins/ServerSkinPopulatePacketHandler.java b/src/main/java/gg/essential/network/connectionmanager/handler/skins/ServerSkinPopulatePacketHandler.java index 7433d61..b7d0124 100644 --- a/src/main/java/gg/essential/network/connectionmanager/handler/skins/ServerSkinPopulatePacketHandler.java +++ b/src/main/java/gg/essential/network/connectionmanager/handler/skins/ServerSkinPopulatePacketHandler.java @@ -12,9 +12,9 @@ package gg.essential.network.connectionmanager.handler.skins; import gg.essential.connectionmanager.common.packet.skin.ServerSkinPopulatePacket; -import gg.essential.gui.wardrobe.Item; import gg.essential.network.connectionmanager.ConnectionManager; import gg.essential.network.connectionmanager.handler.PacketHandler; +import gg.essential.network.connectionmanager.skins.SkinItem; import gg.essential.network.cosmetics.ConversionsKt; import org.jetbrains.annotations.NotNull; @@ -25,7 +25,7 @@ public class ServerSkinPopulatePacketHandler extends PacketHandler=11200 //#if MC>=11700 diff --git a/src/main/java/gg/essential/network/connectionmanager/media/ScreenshotManager.java b/src/main/java/gg/essential/network/connectionmanager/media/ScreenshotManager.java index 3b8669b..22d6cfd 100644 --- a/src/main/java/gg/essential/network/connectionmanager/media/ScreenshotManager.java +++ b/src/main/java/gg/essential/network/connectionmanager/media/ScreenshotManager.java @@ -69,6 +69,7 @@ import gg.essential.util.MinecraftUtils; import gg.essential.util.Multithreading; import gg.essential.util.TemporaryFile; +import gg.essential.util.TimeFormatKt; import gg.essential.util.UUIDUtil; import gg.essential.util.lwjgl3.Lwjgl3Loader; import gg.essential.util.lwjgl3.api.NativeImageReader; @@ -387,7 +388,7 @@ private void completeUpload(@NotNull ClientScreenshotMetadata metadata, ServerMe progressConsumer.accept(new ScreenshotUploadToast.ToastProgress.Step(75)); final DateTime time = metadata.getEditTime() != null ? metadata.getEditTime() : metadata.getTime(); final String identifier = metadata.getLocationMetadata().getIdentifier(); - connectionManager.send(new ClientMediaCreatePacket(packet.getMediaId(), username + "'s Screenshot", "Captured " + HelpersKt.formatDateAndTime(time.toInstant()), new MediaMetadata( + connectionManager.send(new ClientMediaCreatePacket(packet.getMediaId(), username + "'s Screenshot", "Captured " + TimeFormatKt.formatDateAndTime(time.toInstant()), new MediaMetadata( metadata.getAuthorId(), time, new MediaLocationMetadata(metadata.getLocationMetadata().getType().toNetworkType(), identifier, identifier == null ? null : connectionManager.getSpsManager().getHostFromSpsAddress(identifier)), diff --git a/src/main/java/gg/essential/network/connectionmanager/notices/NoticesManager.java b/src/main/java/gg/essential/network/connectionmanager/notices/NoticesManager.java index cb47136..3a9c013 100644 --- a/src/main/java/gg/essential/network/connectionmanager/notices/NoticesManager.java +++ b/src/main/java/gg/essential/network/connectionmanager/notices/NoticesManager.java @@ -14,11 +14,11 @@ import com.google.common.collect.Maps; import gg.essential.Essential; import gg.essential.connectionmanager.common.packet.Packet; -import gg.essential.connectionmanager.common.packet.notices.ClientNoticeDismissPacket; +import gg.essential.connectionmanager.common.packet.notices.ClientNoticeBulkDismissPacket; import gg.essential.connectionmanager.common.packet.notices.ClientNoticeRequestPacket; +import gg.essential.connectionmanager.common.packet.notices.ServerNoticeBulkDismissPacket; import gg.essential.connectionmanager.common.packet.notices.ServerNoticePopulatePacket; import gg.essential.connectionmanager.common.packet.notices.ServerNoticeRemovePacket; -import gg.essential.connectionmanager.common.packet.response.ResponseActionPacket; import gg.essential.gui.elementa.state.v2.MutableState; import gg.essential.gui.elementa.state.v2.State; import gg.essential.gui.state.Sale; @@ -44,11 +44,14 @@ import static gg.essential.gui.elementa.state.v2.StateKt.mutableStateOf; -public class NoticesManager implements NetworkedManager { +public class NoticesManager implements NetworkedManager, INoticesManager { @NotNull private final Map notices = Maps.newConcurrentMap(); + @NotNull + private final Set dismissNoticesQueue = new HashSet<>(); + @NotNull private final ConnectionManager connectionManager; @@ -150,7 +153,8 @@ public List getNotices( .collect(Collectors.toList()); } - public void populateNotices(@NotNull final Collection notices) { + @Override + public void populateNotices(@NotNull final Collection notices) { for (@NotNull final Notice notice : notices) { this.notices.put(notice.getId(), notice); for (NoticeListener listener : listeners) { @@ -159,6 +163,7 @@ public void populateNotices(@NotNull final Collection notices) { } } + @Override public void removeNotices(@Nullable final Set noticeIds) { if (noticeIds == null || noticeIds.isEmpty()) { for (Notice value : notices.values()) { @@ -181,17 +186,47 @@ public void removeNotices(@Nullable final Set noticeIds) { } } + @Override public void dismissNotice(String noticeId) { - this.connectionManager.send(new ClientNoticeDismissPacket(noticeId), packet -> { - if (packet.isPresent()) { - Packet packet1 = packet.get(); - if (packet1 instanceof ResponseActionPacket && ((ResponseActionPacket) packet1).isSuccessful()) { - this.notices.remove(noticeId); + dismissNotices(SetsKt.setOf(noticeId)); + } + + public void dismissNotices(Set noticeIds) { + dismissNoticesQueue.addAll(noticeIds); + flushDismissNotices(); + } + + public void flushDismissNotices() { + if (dismissNoticesQueue.isEmpty()) { + return; + } + final Set notices = new HashSet<>(dismissNoticesQueue); + this.dismissNoticesQueue.clear(); + this.connectionManager.send(new ClientNoticeBulkDismissPacket(notices), maybePacket -> { + if (maybePacket.isPresent()) { + Packet packet = maybePacket.get(); + if (packet instanceof ServerNoticeBulkDismissPacket) { + ServerNoticeBulkDismissPacket serverNoticeBulkDismissPacket = (ServerNoticeBulkDismissPacket) packet; + for (String noticeId : serverNoticeBulkDismissPacket.getNoticeIds()) { + this.notices.remove(noticeId); + } + for (ServerNoticeBulkDismissPacket.ErrorDetails error : serverNoticeBulkDismissPacket.getErrors()) { + switch (error.getReason()) { + case "NOTICE_NOT_FOUND": + case "NOTICE_ALREADY_DISMISSED": { + this.notices.remove(error.getNoticeId()); + break; + } + default: { + Essential.logger.error("Notice unable to be dismissed: NoticeId: {}, Reason: {}", error.getNoticeId(), error.getReason()); + break; + } + } + } return; } } - Essential.logger.error("Unexpected notice response: " + packet); - + Essential.logger.error("Unexpected notice response: {}", maybePacket); }); } @@ -227,17 +262,6 @@ public void onConnected() { } - interface NoticeListener { - void noticeAdded(Notice notice); - - void noticeRemoved(Notice notice); - - void onConnect(); - - default void resetState() { - } - } - public class CosmeticNotices implements NoticeListener { private final String METADATA_KEY = "cosmetic_id"; diff --git a/src/main/java/gg/essential/network/connectionmanager/relationship/RelationshipManager.java b/src/main/java/gg/essential/network/connectionmanager/relationship/RelationshipManager.java index 0b155ea..81093de 100644 --- a/src/main/java/gg/essential/network/connectionmanager/relationship/RelationshipManager.java +++ b/src/main/java/gg/essential/network/connectionmanager/relationship/RelationshipManager.java @@ -47,6 +47,8 @@ import java.util.UUID; import java.util.concurrent.CompletableFuture; +import static gg.essential.network.connectionmanager.relationship.RelationshipResponseKt.displayToast; + // TODO: Should we have individual maps for the RelationshipType.FRIENDS and BLOCKED ? // _or_ perhaps we just have 1 map for all relationships and then sort through them // when / if necessary? @@ -410,7 +412,7 @@ public CompletableFuture deleteRelationship(@NotNull final Essential.logger.error("Unknown relationshipErrorResponse reason: " + reason); } RelationshipResponse relationshipResponse = new RelationshipResponse(FriendRequestState.ERROR_HANDLED, relationshipErrorResponse); - relationshipResponse.displayToast(targetUUID); + displayToast(relationshipResponse, targetUUID); future.complete(relationshipResponse); } else { future.complete(new RelationshipResponse(FriendRequestState.ERROR_UNHANDLED, "An unknown error occurred. Please contact support if issues persist.")); diff --git a/src/main/java/gg/essential/network/connectionmanager/sps/SPSManager.java b/src/main/java/gg/essential/network/connectionmanager/sps/SPSManager.java index dad270b..39254f8 100644 --- a/src/main/java/gg/essential/network/connectionmanager/sps/SPSManager.java +++ b/src/main/java/gg/essential/network/connectionmanager/sps/SPSManager.java @@ -20,13 +20,13 @@ import gg.essential.connectionmanager.common.packet.telemetry.ClientTelemetryPacket; import gg.essential.connectionmanager.common.packet.upnp.*; import gg.essential.data.SPSData; -import gg.essential.elementa.state.BasicState; -import gg.essential.elementa.state.State; import gg.essential.event.network.server.ServerLeaveEvent; import gg.essential.event.sps.PlayerJoinSessionEvent; import gg.essential.event.sps.PlayerLeaveSessionEvent; import gg.essential.event.sps.SPSStartEvent; -import gg.essential.gui.common.WeakState; +import gg.essential.gui.elementa.state.v2.MutableState; +import gg.essential.gui.elementa.state.v2.State; +import gg.essential.gui.elementa.state.v2.StateKt; import gg.essential.gui.friends.state.IStatusManager; import gg.essential.gui.multiplayer.EssentialMultiplayerGui; import gg.essential.mixins.transformers.server.integrated.LanConnectionsAccessor; @@ -126,7 +126,7 @@ public class SPSManager extends StateCallbackManager implements private String serverStatusResponse; private final Set oppedPlayers = new HashSet<>(); - private final Map> onlinePlayerStates = new HashMap<>(); + private final Map> onlinePlayerStates = new HashMap<>(); private boolean shareResourcePack = false; @@ -290,7 +290,7 @@ public synchronized void reinviteUsers(Set users) { // Only revoke invites for users that aren't already in the session, otherwise they'd get kicked Set offlineUsers = users.stream() - .filter(uuid -> getInvitedUsers().contains(uuid) && !getOnlineState(uuid).get()) + .filter(uuid -> getInvitedUsers().contains(uuid) && !getOnlineState(uuid).getUntracked()) .collect(Collectors.toSet()); // No need to refresh the whitelist and persist settings when revoking since we'll immediately reinvite the users revokeInvites(offlineUsers); @@ -421,6 +421,7 @@ public synchronized void updateLocalSession(@NotNull String ip, int port) { } public synchronized void closeLocalSession() { + IntegratedServer server = Minecraft.getMinecraft().getIntegratedServer(); UPnPSession oldSession = this.localSession; if (oldSession != null) { @@ -647,7 +648,11 @@ public void updateWorldGameRules(GameRules gameRules, Map gameRu //#if MC<=11202 immutableGameRules.forEach(gameRules::setOrCreateGameRule); //#else + //#if MC>=12102 + //$$ integratedServer.getGameRules().accept(new GameRules.Visitor() { + //#else //$$ GameRules.visitAll(new GameRules.IRuleEntryVisitor() { + //#endif //$$ @Override //$$ public > void visit(GameRules.RuleKey key, GameRules.RuleType type) { //$$ GameRules.IRuleEntryVisitor.super.visit(key, type); @@ -746,24 +751,24 @@ public Set getOppedPlayers() { /** * @param uuid UUID player to get the online state of - * @return a weak state with value of whether the specified player is online or not + * @return a state with value of whether the specified player is online or not */ - public WeakState getOnlineState(UUID uuid) { + public State getOnlineState(UUID uuid) { if (uuid.equals(UUIDUtil.getClientUUID())) { - return new WeakState<>(new BasicState<>(true)); + return StateKt.stateOf(true); } - return new WeakState<>(onlinePlayerStates.computeIfAbsent(uuid, k -> new BasicState<>(false))); + return onlinePlayerStates.computeIfAbsent(uuid, k -> StateKt.mutableStateOf(false)); } @Subscribe public void onPlayerJoinSession(PlayerJoinSessionEvent event) { - onlinePlayerStates.computeIfAbsent(event.getProfile().getId(), k -> new BasicState<>(true)).set(true); - maxConcurrentGuests = (int) Math.max(maxConcurrentGuests, onlinePlayerStates.values().stream().filter(State::get).count()); + onlinePlayerStates.computeIfAbsent(event.getProfile().getId(), k -> StateKt.mutableStateOf(true)).set(true); + maxConcurrentGuests = (int) Math.max(maxConcurrentGuests, onlinePlayerStates.values().stream().filter(State::getUntracked).count()); } @Subscribe public void onPlayerLeaveSession(PlayerLeaveSessionEvent event) { - final State onlineState = onlinePlayerStates.remove(event.getProfile().getId()); + final MutableState onlineState = onlinePlayerStates.remove(event.getProfile().getId()); if (onlineState != null) { onlineState.set(false); } diff --git a/src/main/java/gg/essential/network/connectionmanager/telemetry/TelemetryManager.java b/src/main/java/gg/essential/network/connectionmanager/telemetry/TelemetryManager.java index 216018b..e47859d 100644 --- a/src/main/java/gg/essential/network/connectionmanager/telemetry/TelemetryManager.java +++ b/src/main/java/gg/essential/network/connectionmanager/telemetry/TelemetryManager.java @@ -201,13 +201,13 @@ private void queueInstallerTelemetryPacket() { // We go async, since we are reading a file Multithreading.runAsync(() -> { try { - Path installerMetadataPath = Essential.getInstance().getBaseDir().toPath().resolve("installer-metadata.json").toRealPath(); + Path installerMetadataPath = Essential.getInstance().getBaseDir().toPath().resolve("installer-metadata.json"); if (Files.notExists(installerMetadataPath)) return; // Calculate the sha-1 checksum of the current game directory in the same way the installer does. - byte[] pathBytes = installerMetadataPath.toString().getBytes(StandardCharsets.UTF_8); + byte[] pathBytes = installerMetadataPath.toRealPath().toString().getBytes(StandardCharsets.UTF_8); byte[] pathChecksumBytes = MessageDigest.getInstance("SHA-1").digest(pathBytes); StringBuilder pathChecksumBuilder = new StringBuilder(); for (byte checksumByte : pathChecksumBytes) { diff --git a/src/main/java/gg/essential/util/LoginUtil.java b/src/main/java/gg/essential/util/LoginUtil.java index fb14fdd..20a751b 100644 --- a/src/main/java/gg/essential/util/LoginUtil.java +++ b/src/main/java/gg/essential/util/LoginUtil.java @@ -21,10 +21,14 @@ import java.nio.charset.StandardCharsets; public class LoginUtil { + private static final String JOIN_URL = System.getProperty( + "essential.mojang_join_url", + "https://sessionserver.mojang.com/session/minecraft/join" + ); public static int joinServer(String token, String uuid, String serverHash) { try { - URL url = new URL("https://sessionserver.mojang.com/session/minecraft/join"); + URL url = new URL(JOIN_URL); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("POST"); diff --git a/src/main/kotlin/gg/essential/commands/impl/SPSHostCommands.kt b/src/main/kotlin/gg/essential/commands/impl/SPSHostCommands.kt index 4f3c52b..4558aca 100644 --- a/src/main/kotlin/gg/essential/commands/impl/SPSHostCommands.kt +++ b/src/main/kotlin/gg/essential/commands/impl/SPSHostCommands.kt @@ -260,7 +260,7 @@ object CommandSession : Command("esession") { val username = UUIDUtil.getName(invitedUser).await() val colorPrefix = when { invitedUser == UUIDUtil.getClientUUID() -> ChatColor.AQUA - spsManager.getOnlineState(invitedUser).get() -> ChatColor.GREEN + spsManager.getOnlineState(invitedUser).getUntracked() -> ChatColor.GREEN else -> ChatColor.GRAY } val suffix = when(invitedUser) { diff --git a/src/main/kotlin/gg/essential/config/McEssentialConfig.kt b/src/main/kotlin/gg/essential/config/McEssentialConfig.kt new file mode 100644 index 0000000..328b89b --- /dev/null +++ b/src/main/kotlin/gg/essential/config/McEssentialConfig.kt @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.config + +import com.sparkuniverse.toolbox.relationships.enums.FriendRequestPrivacySetting +import gg.essential.Essential +import gg.essential.commands.EssentialCommandRegistry +import gg.essential.config.EssentialConfig.autoUpdate +import gg.essential.config.EssentialConfig.autoUpdateState +import gg.essential.config.EssentialConfig.discordRichPresenceState +import gg.essential.config.EssentialConfig.essentialEnabledState +import gg.essential.config.EssentialConfig.friendRequestPrivacyState +import gg.essential.config.EssentialConfig.ownCosmeticsHiddenStateWithSource +import gg.essential.connectionmanager.common.packet.relationships.privacy.FriendRequestPrivacySettingPacket +import gg.essential.connectionmanager.common.packet.response.ResponseActionPacket +import gg.essential.data.OnboardingData +import gg.essential.data.OnboardingData.hasAcceptedTos +import gg.essential.elementa.components.Window +import gg.essential.gui.elementa.state.v2.ReferenceHolderImpl +import gg.essential.gui.modal.discord.DiscordActivityStatusModal +import gg.essential.gui.modals.TOSModal +import gg.essential.gui.notification.Notifications +import gg.essential.gui.notification.error +import gg.essential.gui.notification.sendTosNotification +import gg.essential.gui.vigilancev2.VigilanceV2SettingsGui +import gg.essential.util.AutoUpdate +import gg.essential.util.GuiUtil + +object McEssentialConfig { + private val referenceHolder = ReferenceHolderImpl() + + @JvmOverloads + fun gui(initialCategory: String? = null): VigilanceV2SettingsGui = VigilanceV2SettingsGui(EssentialConfig.gui, initialCategory) + + fun hookUp() { + EssentialConfig.doRevokeTos = ::revokeTos + + friendRequestPrivacyState.onSetValue(referenceHolder) { it -> + if (hasAcceptedTos()) { + val connectionManager = Essential.getInstance().connectionManager + val privacy = FriendRequestPrivacySetting.values()[it] + + connectionManager.send(FriendRequestPrivacySettingPacket(privacy)) { + val get = it.orElse(null) + if (get == null || !(get is ResponseActionPacket && get.isSuccessful)) { + Notifications.error("Error", "An unexpected error occurred. Please try again.") + } + } + } + } + + ownCosmeticsHiddenStateWithSource.onSetValue(referenceHolder) { (hidden, setByUser) -> + if (Essential.getInstance().connectionManager.isAuthenticated) { + Essential.getInstance().connectionManager.cosmeticsManager.setOwnCosmeticVisibility(false, !hidden) + } else { + if (!setByUser) return@onSetValue // infra/mod may set whatever it wants, only the user is getting checked + if (hasAcceptedTos()) { + Notifications.error( + "Essential Network Error", + "Unable to establish connection with the Essential Network." + ) + } else { + fun showTOS() = GuiUtil.pushModal { TOSModal(it, unprompted = false, requiresAuth = true, {}) } + if (GuiUtil.openedScreen() == null) { + // Show a notification when we're not in any menu, so it's less intrusive + sendTosNotification { showTOS() } + } else { + showTOS() + } + } + EssentialConfig.ownCosmeticsHidden = !hidden + } + } + + discordRichPresenceState.onSetValue(referenceHolder) { enabled -> + if (!enabled) return@onSetValue + + GuiUtil.pushModal { DiscordActivityStatusModal(it) } + } + + essentialEnabledState.onSetValue(referenceHolder) { enabling -> + Window.enqueueRenderOperation { toggleEssential(enabling) } + } + + autoUpdate = AutoUpdate.autoUpdate.get() + autoUpdateState.onSetValue(referenceHolder) { shouldAutoUpdate -> + if (shouldAutoUpdate != AutoUpdate.autoUpdate.get()) { + // User explicitly changed the value + // Delayed to allow setAutoUpdate to confirm the value of the autoUpdate setting + Window.enqueueRenderOperation { + AutoUpdate.setAutoUpdates(shouldAutoUpdate) + } + } + } + } + + private fun checkSPS(): Boolean { + return if (Essential.getInstance().connectionManager.spsManager.localSession != null) { + Notifications.error("Error", "You cannot disable Essential while hosting a world.") + false + } else true + } + + private fun toggleEssential(enabling: Boolean) { + // Trying to disable Essential while in an SPS world + if (!enabling && !checkSPS()) { + EssentialConfig.essentialEnabled = true + return + } + + EssentialConfig.essentialEnabled = enabling + + Essential.getInstance().keybindingRegistry.refreshBinds() + (Essential.getInstance().commandRegistry() as EssentialCommandRegistry).checkMiniCommands() + Essential.getInstance().checkListeners() + + if (!enabling) { + Essential.getInstance().connectionManager.onTosRevokedOrEssentialDisabled() + } + } + + private fun revokeTos() { + if (checkSPS()) { + OnboardingData.setDeniedTos() + Essential.getInstance().connectionManager.onTosRevokedOrEssentialDisabled() + } + } +} diff --git a/src/main/kotlin/gg/essential/gui/common/CosmeticRenderPreview.kt b/src/main/kotlin/gg/essential/gui/common/CosmeticRenderPreview.kt index efcc6ac..028fea0 100644 --- a/src/main/kotlin/gg/essential/gui/common/CosmeticRenderPreview.kt +++ b/src/main/kotlin/gg/essential/gui/common/CosmeticRenderPreview.kt @@ -12,6 +12,7 @@ package gg.essential.gui.common import com.mojang.authlib.GameProfile +import dev.folomeev.kotgl.matrix.vectors.vecUnitY import gg.essential.api.profile.wrapped import gg.essential.cosmetics.CosmeticId import gg.essential.cosmetics.source.ConfigurableCosmeticsSource @@ -33,6 +34,8 @@ import gg.essential.mod.cosmetics.preview.PerspectiveCamera import gg.essential.mod.cosmetics.settings.CosmeticSetting import gg.essential.gui.util.onAnimationFrame import gg.essential.gui.wardrobe.WardrobeState +import gg.essential.model.util.Quaternion +import gg.essential.model.util.rotateBy import java.util.* fun LayoutScope.skinRenderPreview(skin: Item.SkinItem, modifier: Modifier = Modifier) { @@ -49,11 +52,17 @@ fun LayoutScope.skinRenderPreview(skin: Item.SkinItem, modifier: Modifier = Modi } fun LayoutScope.outfitRenderPreview(state: WardrobeState, outfit: Item.OutfitItem, modifier: Modifier = Modifier) { - fullBodyRenderPreview(state, outfit.skin, outfit.cosmetics, outfit.settings, false, modifier) + fullBodyRenderPreview( + state, outfit.skin, outfit.cosmetics, outfit.settings, + showEmotes = false, constantRotation = false, modifier = modifier + ) } fun LayoutScope.bundleRenderPreview(state: WardrobeState, bundle: Item.Bundle, modifier: Modifier = Modifier) { - fullBodyRenderPreview(state, bundle.skin.toMod(), bundle.cosmetics, bundle.settings, true, modifier) + fullBodyRenderPreview( + state, bundle.skin.toMod(), bundle.cosmetics, bundle.settings, + showEmotes = true, constantRotation = bundle.rotateOnPreview, modifier = modifier + ) } fun LayoutScope.fullBodyRenderPreview( @@ -62,6 +71,7 @@ fun LayoutScope.fullBodyRenderPreview( cosmeticsMap: Map, settings: Map>, showEmotes: Boolean, + constantRotation: Boolean = false, modifier: Modifier = Modifier ) { val loading = mutableStateOf(cosmeticsMap.isNotEmpty()) @@ -83,7 +93,6 @@ fun LayoutScope.fullBodyRenderPreview( profile = BasicState(profile) ).apply { setRotations(0f, 0f) - perspectiveCamera = PerspectiveCamera.forCosmeticSlot(CosmeticSlot.FULL_BODY) cosmeticsSource = ConfigurableCosmeticsSource().apply { val visibleCosmeticIds = if (showEmotes) cosmeticsMap else cosmeticsMap - CosmeticSlot.EMOTE effect(stateScope) { @@ -92,6 +101,20 @@ fun LayoutScope.fullBodyRenderPreview( // We want the player preview to be rendered with cosmetics even if the user has globally disabled them. shouldOverrideRenderCosmeticsCheck = true } + + if (constantRotation) { + val fullRotationMillis = 10000.0 + val angleStep = 360.0 / fullRotationMillis + onAnimationFrame { + val angle = ((((System.currentTimeMillis() - state.wardrobeOpenTime) * angleStep) % 360.0) * Math.PI / 180.0).toFloat() + val rotation = Quaternion.fromAxisAngle(vecUnitY(), angle) + perspectiveCamera = with(PerspectiveCamera.forCosmeticSlot(CosmeticSlot.FULL_BODY)) { + copy(camera = camera.rotateBy(rotation), target = target.rotateBy(rotation)) + } + } + } else { + perspectiveCamera = PerspectiveCamera.forCosmeticSlot(CosmeticSlot.FULL_BODY) + } }(Modifier.then(emulatedUI3DPlayerModifierState)) if_(loading) { diff --git a/src/main/kotlin/gg/essential/gui/common/UI3DPlayer.kt b/src/main/kotlin/gg/essential/gui/common/UI3DPlayer.kt index b518890..2f1bbe0 100644 --- a/src/main/kotlin/gg/essential/gui/common/UI3DPlayer.kt +++ b/src/main/kotlin/gg/essential/gui/common/UI3DPlayer.kt @@ -74,6 +74,7 @@ import gg.essential.universal.UMatrixStack import gg.essential.universal.UResolution import gg.essential.util.Client import gg.essential.util.ModLoaderUtil +import gg.essential.util.getPerspective; import gg.essential.util.identifier import gg.essential.util.orNull import gg.essential.util.toUC @@ -102,6 +103,10 @@ import kotlin.math.PI import kotlin.random.Random import java.util.* +//#if MC>=12102 +//$$ import com.mojang.blaze3d.systems.ProjectionType +//#endif + //#if MC>=12002 //$$ import org.joml.Quaternionf //$$ import gg.essential.util.thenAcceptOnMainThread @@ -357,7 +362,11 @@ open class UI3DPlayer( //$$ projectionMatrix.peek().model.mul(Matrix4f.perspective(camera.fov.toDouble(), getWidth() / getHeight(), 0.5f, 20f)) //#endif - //#if MC>=12000 + //#if MC>=12102 + //$$ val orgProjectionMatrix = RenderSystem.getProjectionMatrix() + //$$ val orgProjectionType = RenderSystem.getProjectionType() + //$$ RenderSystem.setProjectionMatrix(projectionMatrix.peek().model, ProjectionType.PERSPECTIVE) + //#elseif MC>=12000 //$$ val orgProjectionMatrix = RenderSystem.getProjectionMatrix() //$$ val orgVertexSorting = RenderSystem.getVertexSorting() //$$ RenderSystem.setProjectionMatrix(projectionMatrix.peek().model, VertexSorter.BY_DISTANCE) @@ -381,7 +390,9 @@ open class UI3DPlayer( } isRenderingPerspective = false - //#if MC>=12000 + //#if MC>=12102 + //$$ RenderSystem.setProjectionMatrix(orgProjectionMatrix, orgProjectionType); + //#elseif MC>=12000 //$$ RenderSystem.setProjectionMatrix(orgProjectionMatrix, orgVertexSorting); //#elseif MC>=11700 //$$ RenderSystem.setProjectionMatrix(orgProjectionMatrix); @@ -659,7 +670,7 @@ open class UI3DPlayer( val camera = perspectiveCamera ?: rotationAngleCamera val cameraPos = camera.camera.rotateBy(realRotation).plus(realPosition) - particleSystem.render(stack, cameraPos, realRotation * camera.rotation, vertexConsumerProvider) + particleSystem.render(stack, cameraPos, realRotation * camera.rotation, vertexConsumerProvider, UUID(0, 0), false) UGraphics.disableDepth() } diff --git a/src/main/kotlin/gg/essential/gui/common/modal/OpenLinkModal.kt b/src/main/kotlin/gg/essential/gui/common/modal/OpenLinkModal.kt index ee3dac2..fc6e08f 100644 --- a/src/main/kotlin/gg/essential/gui/common/modal/OpenLinkModal.kt +++ b/src/main/kotlin/gg/essential/gui/common/modal/OpenLinkModal.kt @@ -11,9 +11,6 @@ */ package gg.essential.gui.common.modal -import gg.essential.Essential -import gg.essential.api.gui.NotificationType -import gg.essential.api.gui.Slot import gg.essential.config.EssentialConfig import gg.essential.gui.EssentialPalette import gg.essential.gui.common.StyledButton @@ -33,13 +30,11 @@ import gg.essential.gui.layoutdsl.row import gg.essential.gui.layoutdsl.shadow import gg.essential.gui.layoutdsl.text import gg.essential.gui.layoutdsl.width -import gg.essential.gui.notification.Notifications -import gg.essential.gui.notification.toastButton import gg.essential.gui.overlay.ModalManager -import gg.essential.universal.UDesktop import gg.essential.universal.USound import gg.essential.util.GuiUtil import gg.essential.util.TrustedHostsUtil +import gg.essential.util.openInBrowser import java.awt.Color import java.net.URI @@ -75,7 +70,7 @@ class OpenLinkModal( Modifier.width(91f).onLeftClick { USound.playButtonPress() - browse(uri, successfulToast = true) + openInBrowser(uri) close() }, style = StyledButton.Style.BLUE, @@ -95,26 +90,10 @@ class OpenLinkModal( TrustedHostsUtil.getTrustedHosts().any { trustedHost -> trustedHost.domains.any { it == uri.host } } if (isTrusted) { - browse(uri, true) + openInBrowser(uri) } else if (EssentialConfig.linkWarning) { GuiUtil.pushModal { OpenLinkModal(it, uri) } } } - - fun browse(uri: URI, successfulToast: Boolean = false) { - if (!UDesktop.browse(uri)) { - Essential.logger.error("Failed to open $uri") - Notifications.pushPersistentToast("Can't open browser", "Unable to open link in browser.", {}, {}) { - type = NotificationType.WARNING - withCustomComponent(Slot.ACTION, toastButton("Copy Link") { - UDesktop.setClipboardString(uri.toString()) - }) - } - } else if (successfulToast) { - Notifications.push("", "Link opened in browser") { - withCustomComponent(Slot.ICON, EssentialPalette.JOIN_ARROW_5X.create()) - } - } - } } } diff --git a/src/main/kotlin/gg/essential/gui/emotes/EmoteWheel.kt b/src/main/kotlin/gg/essential/gui/emotes/EmoteWheel.kt index c42a119..fd29ca2 100644 --- a/src/main/kotlin/gg/essential/gui/emotes/EmoteWheel.kt +++ b/src/main/kotlin/gg/essential/gui/emotes/EmoteWheel.kt @@ -32,7 +32,6 @@ import gg.essential.gui.elementa.state.v2.ReferenceHolderImpl import gg.essential.gui.elementa.state.v2.State import gg.essential.gui.elementa.state.v2.combinators.map import gg.essential.gui.elementa.state.v2.memo -import gg.essential.gui.elementa.state.v2.mutableStateOf import gg.essential.gui.elementa.state.v2.stateOf import gg.essential.gui.elementa.state.v2.toListState import gg.essential.gui.layoutdsl.Alignment @@ -65,7 +64,6 @@ import gg.essential.gui.notification.error import gg.essential.gui.util.onAnimationFrame import gg.essential.gui.wardrobe.Wardrobe import gg.essential.gui.wardrobe.WardrobeCategory -import gg.essential.mod.cosmetics.CosmeticOutfit import gg.essential.mod.cosmetics.CosmeticSlot import gg.essential.mod.cosmetics.settings.CosmeticProperty import gg.essential.model.BedrockModel @@ -87,8 +85,14 @@ import gg.essential.util.textLiteral import gg.essential.util.toState import gg.essential.vigilance.utils.onLeftClick import net.minecraft.client.entity.AbstractClientPlayer +import net.minecraft.item.EnumAction +import net.minecraft.item.ItemStack import java.util.concurrent.TimeUnit +//#if MC>=11400 +//$$ import net.minecraft.item.CrossbowItem +//$$ import net.minecraft.item.Items +//#endif class EmoteWheel : WindowScreen( version = ElementaVersion.V6, @@ -97,13 +101,12 @@ class EmoteWheel : WindowScreen( ) { private val essential = Essential.getInstance() private val cosmeticManager = essential.connectionManager.cosmeticsManager + private val emoteWheelManager = essential.connectionManager.emoteWheelManager private val keybind = essential.keybindingRegistry.openEmoteWheel private val debug = BasicState(false) - private val selectedEmoteWheel = mutableStateOf(cosmeticManager.emoteWheels.indexOfFirst { it.isSelected }) - private val emoteModelsForCurrentWheel: ListState?> = memo { - val emoteIds = cosmeticManager.emoteWheels.elementAtOrNull(selectedEmoteWheel())?.slots + val emoteIds = emoteWheelManager.selectedEmoteWheel()?.slots ?: return@memo emptyList() emoteIds.map { emoteId -> @@ -150,7 +153,7 @@ class EmoteWheel : WindowScreen( box(Modifier.height(25f)) { column(Modifier.alignVertical(Alignment.End).childBasedWidth(5f).color(EssentialPalette.BLACK.withAlpha(0.7f))) { spacer(height = 4f) - text(selectedEmoteWheel.map { "Wheel #${it + 1}" }, Modifier.color(EssentialPalette.WHITE).shadow(EssentialPalette.TEXT_SHADOW)) + text(emoteWheelManager.selectedEmoteWheelIndex.map { "Wheel #${it + 1}" }, Modifier.color(EssentialPalette.WHITE).shadow(EssentialPalette.TEXT_SHADOW)) spacer(height = 3f) } } @@ -196,7 +199,7 @@ class EmoteWheel : WindowScreen( scrollEvent.delta <= -1.0 -> 1 else -> return@onMouseScroll } - selectedEmoteWheel.set(cosmeticManager.shiftSelectedEmoteWheel(shiftValue)) + emoteWheelManager.shiftSelectedEmoteWheel(shiftValue) } } @@ -212,7 +215,7 @@ class EmoteWheel : WindowScreen( } override fun onScreenClose() { - cosmeticManager.flushSelectedEmoteWheel(false) + emoteWheelManager.flushSelectedEmoteWheel(false) super.onScreenClose() } @@ -237,7 +240,7 @@ class EmoteWheel : WindowScreen( val arrow = if (left) EssentialPalette.ARROW_LEFT_4X7 else EssentialPalette.ARROW_RIGHT_4X7 box(Modifier.width(15f).heightAspect(1f).color(EssentialPalette.BLACK.withAlpha(0.7f)).hoverScope().whenHovered(outline)) { icon(arrow, (Modifier.alignHorizontal(Alignment.Center(!left)).alignVertical(Alignment.Center)).color(EssentialPalette.TEXT_HIGHLIGHT)) - }.onLeftClick { selectedEmoteWheel.set(cosmeticManager.shiftSelectedEmoteWheel(if (left) -1 else 1)) } + }.onLeftClick { emoteWheelManager.shiftSelectedEmoteWheel(if (left) -1 else 1) } } override fun doesGuiPauseGame(): Boolean { @@ -247,8 +250,6 @@ class EmoteWheel : WindowScreen( companion object { private const val emoteTransitionTimeMs = 0L - const val SLOTS = 8 - @JvmField var isPlayerArmRendering = false @JvmField @@ -276,7 +277,7 @@ class EmoteWheel : WindowScreen( // We allow the user to play emotes if they were connected to the CM before (they have emote wheels loaded). val connectionManager = Essential.getInstance().connectionManager - if (connectionManager.cosmeticsManager.emoteWheels.isEmpty() && !connectionManager.isAuthenticated) { + if (connectionManager.emoteWheelManager.orderedEmoteWheels.getUntracked().isEmpty() && !connectionManager.isAuthenticated) { Notifications.error( "Essential Network Error", "Unable to establish connection with the Essential Network." @@ -298,6 +299,7 @@ class EmoteWheel : WindowScreen( @JvmStatic fun canEmote(player: AbstractClientPlayer): Boolean { return (player.isEntityAlive && !player.isSpectator && !player.isSneaking && !player.isPlayerSleeping && !player.isRiding + && !isUsingItem(player) //#if MC>=11602 //$$ && !player.isSwimming() //#endif @@ -307,6 +309,26 @@ class EmoteWheel : WindowScreen( ) } + private fun isUsingItem(player: AbstractClientPlayer): Boolean = + //#if MC>=11200 + isUsingItem(player, player.heldItemMainhand) || isUsingItem(player, player.heldItemOffhand) + //#else + //$$ isUsingItem(player, player.heldItem) + //#endif + + private fun isUsingItem(player: AbstractClientPlayer, stack: ItemStack?): Boolean { + //#if MC>=11200 + if (stack == null || stack.isEmpty) return false + //#else + //$$ if (stack == null) return false + //#endif + if (player.itemInUseCount > 0 && stack.itemUseAction != EnumAction.NONE) return true + //#if MC>=11400 + //$$ if (stack.item == Items.CROSSBOW && CrossbowItem.isCharged(stack)) return true + //#endif + return false + } + fun getEmoteTransitionTime(cosmetic: Cosmetic): Long { return (cosmetic.property()?.data?.time) ?: emoteTransitionTimeMs @@ -378,20 +400,19 @@ class EmoteWheel : WindowScreen( }, startDelay.toLong(), TimeUnit.MILLISECONDS) } - @JvmOverloads @JvmStatic - fun unequipCurrentEmote(outfit: CosmeticOutfit? = null) { - val cosmeticsManager = Essential.getInstance().connectionManager.cosmeticsManager + fun unequipCurrentEmote() { + val connectionManager = Essential.getInstance().connectionManager + val cosmeticsData = connectionManager.cosmeticsManager.cosmeticsData + val outfitManager = connectionManager.outfitManager - if (cosmeticsManager.equippedCosmetics[CosmeticSlot.EMOTE] == null) return + val outfit = outfitManager.outfits.getUntracked() + .find { CosmeticSlot.EMOTE in it.equippedCosmetics } + ?: return - val cosmetic = cosmeticsManager.getCosmetic(cosmeticsManager.equippedCosmetics[CosmeticSlot.EMOTE] ?: return) ?: return + outfitManager.updateEquippedCosmetic(outfit.id, CosmeticSlot.EMOTE, null) - if (outfit != null) { - cosmeticsManager.updateEquippedCosmetic(outfit, CosmeticSlot.EMOTE, null) - } else { - cosmeticsManager.updateEquippedCosmetic(CosmeticSlot.EMOTE, null) - } + val cosmetic = cosmeticsData.getCosmetic(outfit.equippedCosmetics[CosmeticSlot.EMOTE]!!) ?: return Multithreading.scheduleOnMainThread({ if (EssentialConfig.thirdPersonEmotes) { diff --git a/src/main/kotlin/gg/essential/gui/friends/SocialMenu.kt b/src/main/kotlin/gg/essential/gui/friends/SocialMenu.kt index 9ff6c12..54720ce 100644 --- a/src/main/kotlin/gg/essential/gui/friends/SocialMenu.kt +++ b/src/main/kotlin/gg/essential/gui/friends/SocialMenu.kt @@ -31,6 +31,7 @@ import gg.essential.gui.common.modal.DangerConfirmationEssentialModal import gg.essential.gui.common.modal.configure import gg.essential.gui.elementa.essentialmarkdown.EssentialMarkdown import gg.essential.gui.elementa.state.v2.* +import gg.essential.gui.friends.message.v2.getInfraInstance import gg.essential.gui.friends.previews.ChannelPreview import gg.essential.gui.friends.state.SocialStateManager import gg.essential.gui.friends.tabs.ChatTab diff --git a/src/main/kotlin/gg/essential/gui/friends/message/MessageTitleBar.kt b/src/main/kotlin/gg/essential/gui/friends/message/MessageTitleBar.kt index eddb6a5..1f40042 100644 --- a/src/main/kotlin/gg/essential/gui/friends/message/MessageTitleBar.kt +++ b/src/main/kotlin/gg/essential/gui/friends/message/MessageTitleBar.kt @@ -22,6 +22,7 @@ import gg.essential.gui.common.* import gg.essential.gui.common.constraints.CenterPixelConstraint import gg.essential.gui.common.shadow.EssentialUIText import gg.essential.gui.common.shadow.ShadowEffect +import gg.essential.gui.elementa.state.v2.combinators.map import gg.essential.gui.elementa.state.v2.toV1 import gg.essential.gui.friends.SocialMenu import gg.essential.gui.util.hoveredState diff --git a/src/main/kotlin/gg/essential/gui/friends/message/v2/GiftEmbedImpl.kt b/src/main/kotlin/gg/essential/gui/friends/message/v2/GiftEmbedImpl.kt index 2047b9e..963c0ab 100644 --- a/src/main/kotlin/gg/essential/gui/friends/message/v2/GiftEmbedImpl.kt +++ b/src/main/kotlin/gg/essential/gui/friends/message/v2/GiftEmbedImpl.kt @@ -19,9 +19,9 @@ import gg.essential.gui.common.CosmeticPreview import gg.essential.gui.elementa.state.v2.combinators.map import gg.essential.gui.elementa.state.v2.stateBy import gg.essential.gui.layoutdsl.* -import gg.essential.gui.wardrobe.Item import gg.essential.gui.wardrobe.components.openWardrobeWithHighlight import gg.essential.gui.util.hoveredState +import gg.essential.gui.wardrobe.ItemId import gg.essential.vigilance.utils.onLeftClick class GiftEmbedImpl( @@ -79,7 +79,7 @@ class GiftEmbedImpl( spacer(height = 1f) // Extra pixel for text shadow text("View", Modifier.shadow(EssentialPalette.TEXT_SHADOW)) }.onLeftClick { - cosmetic.get()?.let { openWardrobeWithHighlight(Item.CosmeticOrEmote(it)) } + cosmetic.get()?.let { openWardrobeWithHighlight(ItemId.CosmeticOrEmote(it.id)) } } } diff --git a/src/main/kotlin/gg/essential/gui/friends/message/v2/MessageWrapperImpl.kt b/src/main/kotlin/gg/essential/gui/friends/message/v2/MessageWrapperImpl.kt index 5379474..325e060 100644 --- a/src/main/kotlin/gg/essential/gui/friends/message/v2/MessageWrapperImpl.kt +++ b/src/main/kotlin/gg/essential/gui/friends/message/v2/MessageWrapperImpl.kt @@ -400,8 +400,8 @@ class MessageWrapperImpl( if (channelType != ChannelType.ANNOUNCEMENT) { options.add(replyOption) + options.add(ContextOptionMenu.Divider) } - options.add(ContextOptionMenu.Divider) options.add(copyImageOption) options.add(copyLinkOption) options.add(saveImageOption) diff --git a/src/main/kotlin/gg/essential/gui/friends/message/v2/clientMessage.kt b/src/main/kotlin/gg/essential/gui/friends/message/v2/clientMessage.kt new file mode 100644 index 0000000..d73b835 --- /dev/null +++ b/src/main/kotlin/gg/essential/gui/friends/message/v2/clientMessage.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.gui.friends.message.v2 + +import com.sparkuniverse.toolbox.chat.model.Message +import gg.essential.Essential + +/** + * Creates a message from a network type, excluding the replyTo state. + */ +fun infraInstanceToClient(message: Message): ClientMessage { + val chatManager = Essential.getInstance().connectionManager.chatManager + return ClientMessage( + message.id, + chatManager.getChannel(message.channelId).get(), + message.sender, + message.contents, + SendState.CONFIRMED, + message.replyTargetId?.let { + MessageRef(message.channelId, it) + }, + message.lastEditTime, + ) +} + +fun ClientMessage.getInfraInstance(): Message { + return Essential.getInstance().connectionManager.chatManager.getMessageById(id) ?: Message( + id, + channel.id, + sender, + contents, + true, // So the social menu doesn't try to mark this message as read + replyTo?.messageId, + lastEditTime, + ) +} diff --git a/src/main/kotlin/gg/essential/gui/friends/previews/ChannelPreview.kt b/src/main/kotlin/gg/essential/gui/friends/previews/ChannelPreview.kt index 7b20f44..a9e132e 100644 --- a/src/main/kotlin/gg/essential/gui/friends/previews/ChannelPreview.kt +++ b/src/main/kotlin/gg/essential/gui/friends/previews/ChannelPreview.kt @@ -14,10 +14,8 @@ package gg.essential.gui.friends.previews import com.sparkuniverse.toolbox.chat.model.Channel import com.sparkuniverse.toolbox.chat.model.Message import gg.essential.Essential -import gg.essential.config.LoadsResources import gg.essential.elementa.components.UIBlock import gg.essential.elementa.components.UIContainer -import gg.essential.elementa.components.UIImage import gg.essential.elementa.constraints.* import gg.essential.elementa.dsl.* import gg.essential.elementa.impl.commonmark.node.BlockQuote @@ -61,7 +59,6 @@ import java.time.ZoneId import java.time.format.DateTimeFormatter import java.time.temporal.ChronoUnit import java.util.* -import kotlin.random.Random import gg.essential.gui.elementa.state.v2.stateBy as stateByV2 class ChannelPreview( @@ -107,7 +104,7 @@ class ChannelPreview( if (channel.isAnnouncement()) { EssentialPalette.ANNOUNCEMENT_ICON_8X.create() } else { - newGroupIcon(channel.id) + EssentialPalette.groupIconForChannel(channel.id).create() } } val color = Modifier.whenTrue( @@ -238,14 +235,6 @@ class ChannelPreview( } companion object { - private val groupIcons = listOf("blue", "purple", "red", "yellow") - - @LoadsResources("/assets/essential/textures/friends/group_[a-z]+.png") - fun newGroupIcon(channelId: Long): UIImage { - val name = groupIcons.random(Random(channelId)) - return UIImage.ofResourceCached("/assets/essential/textures/friends/group_$name.png") - } - private val markdownRenderer = TextContentRenderer.builder() .stripNewlines(false) .nodeRendererFactory(::PlainBlockQuoteNodeRenderer) diff --git a/src/main/kotlin/gg/essential/gui/friends/previews/FriendStatus.kt b/src/main/kotlin/gg/essential/gui/friends/previews/FriendStatus.kt index fc9d564..7a92bde 100644 --- a/src/main/kotlin/gg/essential/gui/friends/previews/FriendStatus.kt +++ b/src/main/kotlin/gg/essential/gui/friends/previews/FriendStatus.kt @@ -41,7 +41,7 @@ class FriendStatus( height = ChildBasedMaxSizeConstraint() } effect ScissorEffect() - statusStates.getActivityState(uuid).onSetValueAndNow { + statusStates.getActivityState(uuid).onSetValueAndNow(this) { clearChildren() when (it) { is PlayerActivity.Offline -> { diff --git a/src/main/kotlin/gg/essential/gui/friends/state/MessengerStateManagerImpl.kt b/src/main/kotlin/gg/essential/gui/friends/state/MessengerStateManagerImpl.kt index 7892c36..bbf07d4 100644 --- a/src/main/kotlin/gg/essential/gui/friends/state/MessengerStateManagerImpl.kt +++ b/src/main/kotlin/gg/essential/gui/friends/state/MessengerStateManagerImpl.kt @@ -31,6 +31,8 @@ import gg.essential.gui.elementa.state.v2.toListState import gg.essential.gui.friends.message.v2.ClientMessage import gg.essential.gui.elementa.state.v2.MutableState import gg.essential.gui.elementa.state.v2.State +import gg.essential.gui.friends.message.v2.getInfraInstance +import gg.essential.gui.friends.message.v2.infraInstanceToClient import gg.essential.network.connectionmanager.chat.ChatManager import gg.essential.universal.UMinecraft import gg.essential.util.* @@ -79,7 +81,7 @@ class MessengerStateManagerImpl(private val chatManager: ChatManager) : IMesseng override fun getMessageListState(channelId: Long): ListState { return observableMessageList.computeIfAbsent(channelId) { - val messages = getMessages(channelId)?.map { ClientMessage.fromNetwork(it) }?.toTypedArray() + val messages = getMessages(channelId)?.map { infraInstanceToClient(it) }?.toTypedArray() ?: emptyArray() val baseMessages = mutableListStateOf(*messages) // Filter messages @@ -271,7 +273,7 @@ class MessengerStateManagerImpl(private val chatManager: ChatManager) : IMesseng observableMessageList[channel.id]?.first?.let { messageList -> // Prevent duplicates from being added val index = messageList.getUntracked().indexOfFirst { it.id == message.id } - val newMessage = ClientMessage.fromNetwork(message) + val newMessage = infraInstanceToClient(message) if (index != -1) { messageList.set(index, newMessage) } else { diff --git a/src/main/kotlin/gg/essential/gui/friends/state/RelationshipStateManagerImpl.kt b/src/main/kotlin/gg/essential/gui/friends/state/RelationshipStateManagerImpl.kt index d99bd59..8d07cca 100644 --- a/src/main/kotlin/gg/essential/gui/friends/state/RelationshipStateManagerImpl.kt +++ b/src/main/kotlin/gg/essential/gui/friends/state/RelationshipStateManagerImpl.kt @@ -19,6 +19,8 @@ import gg.essential.gui.notification.iconAndMarkdownBody import gg.essential.network.connectionmanager.relationship.FriendRequestState import gg.essential.network.connectionmanager.relationship.RelationshipManager import gg.essential.network.connectionmanager.relationship.RelationshipResponse +import gg.essential.network.connectionmanager.relationship.displayToast +import gg.essential.network.connectionmanager.relationship.message import gg.essential.util.* import java.time.Instant import java.util.* diff --git a/src/main/kotlin/gg/essential/gui/friends/state/StatusStateManagerImpl.kt b/src/main/kotlin/gg/essential/gui/friends/state/StatusStateManagerImpl.kt index bdb15b8..6debc74 100644 --- a/src/main/kotlin/gg/essential/gui/friends/state/StatusStateManagerImpl.kt +++ b/src/main/kotlin/gg/essential/gui/friends/state/StatusStateManagerImpl.kt @@ -12,9 +12,9 @@ package gg.essential.gui.friends.state import gg.essential.connectionmanager.common.enums.ProfileStatus -import gg.essential.elementa.state.BasicState -import gg.essential.elementa.state.State -import gg.essential.gui.common.ReadOnlyState +import gg.essential.gui.elementa.state.v2.MutableState +import gg.essential.gui.elementa.state.v2.mutableStateOf +import gg.essential.gui.elementa.state.v2.State import gg.essential.network.connectionmanager.profile.ProfileManager import gg.essential.network.connectionmanager.sps.SPSManager import gg.essential.util.AddressUtil @@ -29,16 +29,14 @@ class StatusStateManagerImpl( private val spsManager: SPSManager ) : IStatusStates, IStatusManager { - private val statesMap = mutableMapOf>() + private val statesMap = mutableMapOf>() init { profileManager.registerStateManager(this) spsManager.registerStateManager(this) } - override fun getActivityState(uuid: UUID): ReadOnlyState { - return ReadOnlyState(getWritableState(uuid)) - } + override fun getActivityState(uuid: UUID): State = getWritableState(uuid) override fun getActivity(uuid: UUID): PlayerActivity { val status = profileManager.getStatus(uuid) @@ -85,9 +83,9 @@ class StatusStateManagerImpl( return true } - private fun getWritableState(uuid: UUID): State { + private fun getWritableState(uuid: UUID): MutableState { return statesMap.computeIfAbsent(uuid) { - BasicState(getActivity(uuid)) + mutableStateOf(getActivity(uuid)) } } @@ -100,7 +98,7 @@ class StatusStateManagerImpl( if (it.key == uuid) { return } - val activity = it.value.get() + val activity = it.value.getUntracked() if (activity is PlayerActivity.SPSSession && activity.host == uuid) { refreshActivity(it.key) } diff --git a/src/main/kotlin/gg/essential/gui/friends/title/TitleManagementActions.kt b/src/main/kotlin/gg/essential/gui/friends/title/TitleManagementActions.kt index 131d1d6..4adf960 100644 --- a/src/main/kotlin/gg/essential/gui/friends/title/TitleManagementActions.kt +++ b/src/main/kotlin/gg/essential/gui/friends/title/TitleManagementActions.kt @@ -23,6 +23,7 @@ import gg.essential.gui.notification.iconAndMarkdownBody import gg.essential.network.connectionmanager.relationship.FriendRequestState import gg.essential.network.connectionmanager.relationship.RelationshipErrorResponse import gg.essential.network.connectionmanager.relationship.RelationshipResponse +import gg.essential.network.connectionmanager.relationship.message import gg.essential.util.GuiUtil import gg.essential.util.colored import gg.essential.util.thenAcceptOnMainThread diff --git a/src/main/kotlin/gg/essential/gui/menu/RightSideBar.kt b/src/main/kotlin/gg/essential/gui/menu/RightSideBar.kt index 5df71cf..1514135 100644 --- a/src/main/kotlin/gg/essential/gui/menu/RightSideBar.kt +++ b/src/main/kotlin/gg/essential/gui/menu/RightSideBar.kt @@ -12,7 +12,7 @@ package gg.essential.gui.menu import gg.essential.Essential -import gg.essential.config.EssentialConfig +import gg.essential.config.McEssentialConfig import gg.essential.data.VersionData import gg.essential.elementa.components.UIContainer import gg.essential.elementa.constraints.AspectConstraint @@ -45,14 +45,13 @@ open class RightSideBar(menuType: PauseMenuDisplay.MenuType, menuVisible: State< val connectionManager = Essential.getInstance().connectionManager val collapsed = BasicState(false).map { it } private val hostable = BasicState(menuType == PauseMenuDisplay.MenuType.MAIN) - private val showHostWorldInsteadOfInvite = stateBy { hostable() } private val isHostingWorld = pollingState { connectionManager.spsManager.localSession != null } val essentialTooltip = BasicState("About Essential") val inviteTooltip = stateBy { when { !collapsed() -> "" - showHostWorldInsteadOfInvite() -> "Host World" + hostable() -> "Host World" else -> "Invite Friends" } } @@ -65,7 +64,7 @@ open class RightSideBar(menuType: PauseMenuDisplay.MenuType, menuVisible: State< val worldSettingsVisible = isHostingWorld - private val inviteIcon = showHostWorldInsteadOfInvite.map { + private val inviteIcon = hostable.map { if (it) EssentialPalette.WORLD_8X else EssentialPalette.ENVELOPE_9X7 } @@ -122,7 +121,7 @@ open class RightSideBar(menuType: PauseMenuDisplay.MenuType, menuVisible: State< }.setIcon(BasicState(EssentialPalette.WORLD_8X)) val inviteButton by MenuButton( - showHostWorldInsteadOfInvite.map { if (it) "Host World" else "Invite" }, + hostable.map { if (it) "Host World" else "Invite" }, textAlignment = MenuButton.Alignment.LEFT, ) { PauseMenuDisplay.showInviteOrHostModal(SPSSessionSource.PAUSE_MENU) @@ -152,7 +151,7 @@ open class RightSideBar(menuType: PauseMenuDisplay.MenuType, menuVisible: State< .bindCollapsed(collapsed, 20f) val settings by MenuButton(BasicState("Settings"), textAlignment = MenuButton.Alignment.LEFT) { - GuiUtil.openScreen { EssentialConfig.gui() } + GuiUtil.openScreen { McEssentialConfig.gui() } }.constrain { width = 80.pixels height = 20.pixels diff --git a/src/main/kotlin/gg/essential/gui/modals/EssentialAutoInstalledModal.kt b/src/main/kotlin/gg/essential/gui/modals/EssentialAutoInstalledModal.kt index 2a4345f..ed1ef44 100644 --- a/src/main/kotlin/gg/essential/gui/modals/EssentialAutoInstalledModal.kt +++ b/src/main/kotlin/gg/essential/gui/modals/EssentialAutoInstalledModal.kt @@ -23,7 +23,6 @@ import gg.essential.elementa.state.BasicState import gg.essential.gui.EssentialPalette import gg.essential.gui.common.MenuButton import gg.essential.gui.common.modal.EssentialModal -import gg.essential.gui.common.modal.OpenLinkModal import gg.essential.gui.common.modal.configure import gg.essential.gui.layoutdsl.Arrangement import gg.essential.gui.layoutdsl.Modifier @@ -44,6 +43,7 @@ import gg.essential.universal.ChatColor import gg.essential.util.EssentialContainerUtil import gg.essential.util.GuiUtil import gg.essential.util.ModLoaderUtil +import gg.essential.util.openInBrowser import java.awt.Color import java.net.URI @@ -52,13 +52,10 @@ class EssentialAutoInstalledModal(modalManager: ModalManager) : EssentialModal(m init { configure { primaryButtonText = "Learn More" - titleText = "Essential Mod has been installed,\nbecause its libraries are required" + titleText = "Essential Mod has been installed\nbecause its libraries are required" titleTextColor = EssentialPalette.TEXT primaryButtonAction = { - OpenLinkModal.browse( - URI("https://essential.gg/wiki/installed-by-other-mods"), - true - ) + openInBrowser(URI("https://essential.gg/wiki/installed-by-other-mods")) } } diff --git a/src/main/kotlin/gg/essential/gui/modals/UpdateAvailableModal.kt b/src/main/kotlin/gg/essential/gui/modals/UpdateAvailableModal.kt index 4a924ef..8c77db2 100644 --- a/src/main/kotlin/gg/essential/gui/modals/UpdateAvailableModal.kt +++ b/src/main/kotlin/gg/essential/gui/modals/UpdateAvailableModal.kt @@ -14,11 +14,13 @@ package gg.essential.gui.modals import gg.essential.elementa.components.Window import gg.essential.elementa.constraints.SiblingConstraint import gg.essential.elementa.dsl.pixels +import gg.essential.elementa.state.BasicState import gg.essential.gui.EssentialPalette import gg.essential.gui.common.MenuButton import gg.essential.gui.common.compactFullEssentialToggle import gg.essential.gui.common.modal.ConfirmDenyModal import gg.essential.gui.common.modal.configure +import gg.essential.gui.common.state import gg.essential.gui.elementa.state.v2.mutableStateOf import gg.essential.gui.elementa.state.v2.toV1 import gg.essential.gui.layoutdsl.* @@ -40,6 +42,7 @@ class UpdateAvailableModal(modalManager: ModalManager) : ConfirmDenyModal(modalM primaryButtonText = "Update" primaryButtonStyle = MenuButton.GREEN primaryButtonHoverStyle = MenuButton.LIGHT_GREEN + contentTextSpacingState.rebind(BasicState(17f)) if (AutoUpdate.requiresUpdate()) { titleTextColor = EssentialPalette.MODAL_WARNING @@ -47,23 +50,30 @@ class UpdateAvailableModal(modalManager: ModalManager) : ConfirmDenyModal(modalM val autoUpdate = mutableStateOf(AutoUpdate.autoUpdate.get()) - customContent.layoutAsBox(BasicYModifier { SiblingConstraint(17f) }) { - row { - spacer(width = 3f) - row(Arrangement.spacedBy(6f, FloatPosition.START)) { - text("Auto-updates", Modifier.color(EssentialPalette.TEXT_MID_GRAY).shadow(Color.BLACK)) - box(Modifier.childBasedWidth(3f).childBasedHeight(3f).hoverScope()) { - compactFullEssentialToggle(autoUpdate.toV1(this@UpdateAvailableModal)) + customContent.layoutAsBox(BasicYModifier { SiblingConstraint(15f) }) { + column { + row( + Modifier.childBasedWidth(3f).onLeftClick { + USound.playButtonPress() + autoUpdate.set { !it } + }, + Arrangement.spacedBy(9f), + ) { + text("Auto-updates", Modifier.color(EssentialPalette.TEXT_DISABLED).shadow(Color.BLACK)) + box(Modifier.childBasedHeight(3f).hoverScope()) { + compactFullEssentialToggle( + autoUpdate.toV1(this@UpdateAvailableModal), + offColor = EssentialPalette.TEXT_DISABLED.state() + ) spacer(1f, 1f) } } - }.onLeftClick { - USound.playButtonPress() - autoUpdate.set { !it } + + spacer(height = 14f) } } - spacer.setHeight(17.pixels) + spacer.setHeight(0.pixels) AutoUpdate.changelog.whenCompleteAsync({ changelog, _ -> changelog?.let { contentText = it } diff --git a/src/main/kotlin/gg/essential/gui/notification/Notifications.kt b/src/main/kotlin/gg/essential/gui/notification/Notifications.kt index ea56833..4a80a33 100644 --- a/src/main/kotlin/gg/essential/gui/notification/Notifications.kt +++ b/src/main/kotlin/gg/essential/gui/notification/Notifications.kt @@ -29,7 +29,7 @@ import gg.essential.util.executor import net.minecraft.client.Minecraft import java.util.concurrent.CompletableFuture -object Notifications : Notifications { +object NotificationsImpl : Notifications, NotificationsManager { private const val MAXIMUM_NOTIFICATIONS = 3 private val mc = Minecraft.getMinecraft() @@ -105,7 +105,7 @@ object Notifications : Notifications { } } - fun pushPersistentToast( + override fun pushPersistentToast( title: String, message: String, action: () -> Unit, @@ -230,18 +230,30 @@ object Notifications : Notifications { ) } - fun hide() { + override fun hide() { layer.rendered = false } - fun show() { + override fun show() { layer.rendered = true } - fun hasActiveNotifications(): Boolean { + override fun hasActiveNotifications(): Boolean { return window.children.size > 0 } + override fun removeNotificationById(id: Any) { + notifications.removeIf { it.uniqueId == id } + + window.childrenOfType() + .filter { it.uniqueId == id } + .forEach { it.dismissInstantly() } + + if (blockedNotifications.isNotEmpty()) { + blockedNotifications.add { removeNotificationById(id) } + } + } + private fun hasNotification(uniqueId: Any): Boolean { return notifications.any { it.uniqueId == uniqueId } || window.childrenOfType().any { it.uniqueId == uniqueId } } diff --git a/src/main/kotlin/gg/essential/gui/overlay/ModalManagerImpl.kt b/src/main/kotlin/gg/essential/gui/overlay/ModalManagerImpl.kt index 8aa708c..1b1c553 100644 --- a/src/main/kotlin/gg/essential/gui/overlay/ModalManagerImpl.kt +++ b/src/main/kotlin/gg/essential/gui/overlay/ModalManagerImpl.kt @@ -19,7 +19,7 @@ import gg.essential.elementa.dsl.pixels import gg.essential.elementa.utils.withAlpha import gg.essential.gui.common.modal.Modal import gg.essential.gui.common.modal.defaultEssentialModalFadeTime -import gg.essential.gui.elementa.transitions.FadeInTransition +import gg.essential.gui.transitions.FadeInTransition import gg.essential.util.Client import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers diff --git a/src/main/kotlin/gg/essential/gui/overlay/OverlayManagerImpl.kt b/src/main/kotlin/gg/essential/gui/overlay/OverlayManagerImpl.kt index f23886c..e8cf26b 100644 --- a/src/main/kotlin/gg/essential/gui/overlay/OverlayManagerImpl.kt +++ b/src/main/kotlin/gg/essential/gui/overlay/OverlayManagerImpl.kt @@ -95,9 +95,10 @@ object OverlayManagerImpl : OverlayManager { } /** - * Disposes of any ephemeral layers which no longer have any children in accordance with [EphemeralLayer.onClose]. + * Disposes of any ephemeral layers which no longer have any children in accordance with [EphemeralLayer.onClose] + * and any layers with a Window where [Window.hasErrored] is set to true. */ - private fun cleanupEphemeralLayers() { + private fun cleanupLayers() { // Ephemeral layers may be temporarily empty e.g. when a modal is closed in response to a click and a new // component is scheduled to be opened via `Window.enqueueRenderOperation`. Ordinarily we'd clean up these // layers anyway, because there's no way for us to know that it's only temporarily empty. @@ -116,7 +117,7 @@ object OverlayManagerImpl : OverlayManager { return@removeIf true } } - return@removeIf false + return@removeIf layer.window.hasErrored } } @@ -292,7 +293,7 @@ object OverlayManagerImpl : OverlayManager { private var originalMousePos: Pair? = null private fun preDraw(event: GuiDrawScreenEvent) { - cleanupEphemeralLayers() + cleanupLayers() computeLayersWithTrueMousePos() handleDraw(event.matrixStack, LayerPriority.BelowScreenContent) @@ -332,7 +333,7 @@ object OverlayManagerImpl : OverlayManager { } private fun nonScreenDraw(event: RenderTickEvent) { - cleanupEphemeralLayers() + cleanupLayers() layersWithTrueMousePos = emptySet() // mouse is captured, no one gets to see it // TODO could add more specific events in the HUD rendering code, but we only use Modal and above atm anyway @@ -384,6 +385,19 @@ object OverlayManagerImpl : OverlayManager { @Subscribe fun handleNonScreenDraw(event: RenderTickEvent) { + if (event.isPre) { + return + } + + if (event.isLoadingScreen) { + flushVanillaBuffers() + layersWithTrueMousePos = emptySet() // the loading screen isn't a real screen and can't handle input + // The loading screen is drawn on top of whatever screen is active, so the actual active screen isn't + // visible, so we don't want to render screen-related layers either. + handleDraw(event.matrixStack, LayerPriority.Modal..LayerPriority.Highest) + return + } + if (UScreen.currentScreen != null) { return // more specific GuiDrawScreenEvents will be emitted } diff --git a/src/main/kotlin/gg/essential/gui/screenshot/ScreenshotOverlay.kt b/src/main/kotlin/gg/essential/gui/screenshot/ScreenshotOverlay.kt index 1ea3c41..b67cf5b 100644 --- a/src/main/kotlin/gg/essential/gui/screenshot/ScreenshotOverlay.kt +++ b/src/main/kotlin/gg/essential/gui/screenshot/ScreenshotOverlay.kt @@ -45,6 +45,8 @@ import gg.essential.gui.screenshot.ScreenshotOverlay.animating import gg.essential.gui.screenshot.components.ScreenshotBrowser import gg.essential.gui.screenshot.components.createShareScreenshotModal import gg.essential.gui.screenshot.constraints.AspectPreservingFillConstraint +import gg.essential.gui.screenshot.toast.ScreenshotPreviewAction +import gg.essential.gui.screenshot.toast.ScreenshotPreviewActionSlot import gg.essential.gui.util.hoveredState import gg.essential.gui.util.onAnimationFrame import gg.essential.universal.UResolution @@ -443,42 +445,6 @@ class ScreenshotPreviewToast(val file: File) : ScreenshotToast() { } -enum class ScreenshotPreviewActionSlot(val defaultAction: ScreenshotPreviewAction) { - - TOP_LEFT(ScreenshotPreviewAction.EDIT), - TOP_RIGTH(ScreenshotPreviewAction.FAVORITE), - BOTTOM_LEFT(ScreenshotPreviewAction.COPY_PICTURE), - BOTTOM_RIGHT(ScreenshotPreviewAction.SHARE), - ; - - val action: ScreenshotPreviewAction - get() { - return ScreenshotPreviewAction.values().getOrNull( - when (this) { - TOP_LEFT -> EssentialConfig.screenshotOverlayTopLeftAction - TOP_RIGTH -> EssentialConfig.screenshotOverlayTopRightAction - BOTTOM_LEFT -> EssentialConfig.screenshotOverlayBottomLeftAction - BOTTOM_RIGHT -> EssentialConfig.screenshotOverlayBottomRightAction - } - ) ?: defaultAction - } - -} - -// The order of these actions is used/replicated in the settings. -// Remember to change in both places at once -enum class ScreenshotPreviewAction(val displayName: String) { - - COPY_PICTURE("Copy Picture"), - COPY_LINK("Copy Link"), - FAVORITE("Favorite"), - DELETE("Delete"), - SHARE("Share to Friends"), - EDIT("Edit"), - ; - -} - class ScreenshotUploadToast : UIContainer() { private val maxCompletionDelayMillis = 500 diff --git a/src/main/kotlin/gg/essential/gui/screenshot/components/FocusListComponent.kt b/src/main/kotlin/gg/essential/gui/screenshot/components/FocusListComponent.kt index 08415bf..706c2fa 100644 --- a/src/main/kotlin/gg/essential/gui/screenshot/components/FocusListComponent.kt +++ b/src/main/kotlin/gg/essential/gui/screenshot/components/FocusListComponent.kt @@ -16,7 +16,7 @@ import gg.essential.elementa.constraints.CenterConstraint import gg.essential.elementa.constraints.SiblingConstraint import gg.essential.elementa.dsl.* import gg.essential.elementa.effects.ScissorEffect -import gg.essential.gui.elementa.effects.AlphaEffect +import gg.essential.gui.effects.AlphaEffect import gg.essential.gui.elementa.state.v2.combinators.zip import gg.essential.gui.elementa.state.v2.mutableStateOf import gg.essential.gui.elementa.state.v2.toV1 diff --git a/src/main/kotlin/gg/essential/gui/screenshot/providers/MinecraftWindowedTextureProvider.kt b/src/main/kotlin/gg/essential/gui/screenshot/providers/MinecraftWindowedTextureProvider.kt index 8699e48..713e45c 100644 --- a/src/main/kotlin/gg/essential/gui/screenshot/providers/MinecraftWindowedTextureProvider.kt +++ b/src/main/kotlin/gg/essential/gui/screenshot/providers/MinecraftWindowedTextureProvider.kt @@ -16,6 +16,7 @@ import gg.essential.gui.screenshot.ScreenshotId import gg.essential.gui.screenshot.downsampling.PixelBuffer import gg.essential.gui.screenshot.image.PixelBufferTexture import gg.essential.universal.UMinecraft +import gg.essential.util.RefCounted import gg.essential.util.executor import gg.essential.util.identifier import net.minecraft.client.Minecraft @@ -59,7 +60,7 @@ class MinecraftWindowedTextureProvider( if (windows.isEmpty() && textureManager == null) { return emptyMap() } - val textureManager = textureManager ?: makeAsyncTextureManager().also { textureManager = it } + val textureManager = textureManager ?: AsyncTextureManager().also { textureManager = it } val processed = mutableMapOf() @@ -165,11 +166,11 @@ var asyncErrored = false //$$ ) //#endif -private fun makeAsyncTextureManager(): AsyncTextureManager { +private fun makeUploadBackend(): UploadBackend { val supported = System.getProperty("essential.async_texture_loading")?.toBoolean() ?: true if (!supported || asyncErrored) { - return NotActuallyAsyncTextureManager() + return NotAsyncUploadBackend() } //#if MC>=11600 @@ -203,7 +204,7 @@ private fun makeAsyncTextureManager(): AsyncTextureManager { //$$ //$$ val window = createWindow(null) //$$ if (window != 0L) { - //$$ return AsyncTextureManagerImpl(window) + //$$ return AsyncUploadBackendImpl(window) //$$ } else { //$$ currentHintsFailed = true; //$$ Essential.logger.debug("Current window hints failed, trying GL versions") @@ -226,7 +227,7 @@ private fun makeAsyncTextureManager(): AsyncTextureManager { //$$ workingGlVersion = major to minor //$$ // Clear the GLFW error flag //$$ GLFW.glfwGetError(null) - //$$ return AsyncTextureManagerImpl(window) + //$$ return AsyncUploadBackendImpl(window) //$$ } //$$ } //$$ } @@ -238,7 +239,7 @@ private fun makeAsyncTextureManager(): AsyncTextureManager { //$$ if (version != null) { //$$ val window = createWindow(version) //$$ if (window != 0L) { - //$$ return AsyncTextureManagerImpl(window) + //$$ return AsyncUploadBackendImpl(window) //$$ } //$$ } //$$ @@ -252,14 +253,62 @@ private fun makeAsyncTextureManager(): AsyncTextureManager { //$$ } //$$ } //$$ asyncErrored = true - //$$ return NotActuallyAsyncTextureManager() + //$$ return NotAsyncUploadBackend() //#else - return AsyncTextureManagerImpl() + return AsyncUploadBackendImpl() //#endif } +/** + * A wrapper around a thread with an active OpenGL context. + */ +interface UploadBackend { + /** + * Submits a block to run on the context thread + */ + fun submit(block: () -> Unit) + + /** + * Cleans up the backend's resources, destroying the underlying context + */ + fun cleanup() +} + +abstract class AsyncUploadBackend : UploadBackend { + private val singletonExecutor = Executors.newSingleThreadExecutor { + Thread(it, "Screenshot uploader thread ${nextThreadNumber.getAndIncrement()}") + } + + private var initialized = false + + abstract fun prepareContext() + + abstract fun destroyContext() + + private fun checkInitialized() { + if (!initialized) { + initialized = true + singletonExecutor.execute { prepareContext() } + } + } + + override fun submit(block: () -> Unit) { + checkInitialized() + singletonExecutor.execute { block() } + } + + override fun cleanup() { + singletonExecutor.execute { destroyContext() } + singletonExecutor.shutdown() + } + + companion object { + private val nextThreadNumber = AtomicInteger(1) + } +} + //#if MC<11600 -class AsyncTextureManagerImpl : AsyncTextureManagerBase() { +class AsyncUploadBackendImpl : AsyncUploadBackend() { private val drawable = SharedDrawable(Display.getDrawable()) @@ -273,7 +322,7 @@ class AsyncTextureManagerImpl : AsyncTextureManagerBase() { } //#else -//$$ class AsyncTextureManagerImpl(private val window: Long) : AsyncTextureManagerBase() { +//$$ class AsyncUploadBackendImpl(private val window: Long) : AsyncUploadBackend() { //$$ //$$ override fun prepareContext() { //$$ GLFW.glfwMakeContextCurrent(window) @@ -290,13 +339,21 @@ class AsyncTextureManagerImpl : AsyncTextureManagerBase() { //$$ } //#endif +class NotAsyncUploadBackend : UploadBackend { + override fun submit(block: () -> Unit) { + block() + } + + override fun cleanup() { + + } +} + /** * Utility class for allowing a worker thread to upload textures off the main thread */ -abstract class AsyncTextureManagerBase : AsyncTextureManager { - private val singletonExecutor = Executors.newSingleThreadExecutor() { - Thread(it, "Screenshot uploader thread ${nextThreadNumber.getAndIncrement()}") - } +class AsyncTextureManager { + private val uploadBackend = uploadBackendRefCounted.obtain { makeUploadBackend() } /** * Set of screenshot paths that have been uploaded since @@ -304,29 +361,12 @@ abstract class AsyncTextureManagerBase : AsyncTextureManager { */ private val complete = mutableMapOf() - private var initialized = false - - /** - * Called on the thread that the OpenGL context should be bound to - */ - abstract fun prepareContext() - /** - * Called on the thread with the bound OpenGL context to free up that context + * Schedules the [texture] function to be called on a worker thread. + * The texture object is then loaded by the Minecraft texture manager on the main thread */ - abstract fun destroyContext() - - - private fun checkInitialization() { - if (!initialized) { - initialized = true - singletonExecutor.execute { prepareContext() } - } - } - - override fun upload(path: ScreenshotId, texture: () -> Pair) { - checkInitialization() - singletonExecutor.execute { + fun upload(path: ScreenshotId, texture: () -> Pair) { + uploadBackend.submit { val (pixelBufferTexture, resourceLocation) = texture() GL11.glFlush() @@ -343,15 +383,21 @@ abstract class AsyncTextureManagerBase : AsyncTextureManager { } } - override fun getFinished(): Set { + /** + * Returns the list of paths that had their textures uploaded since the last call to getFinished() + */ + fun getFinished(): Set { //Clone the entries that are loaded return synchronized(complete) { complete.keys.toSet().also { complete.clear() } } } - override fun cleanup() { - singletonExecutor.execute { + /** + * Called to clean the context free the underlying resources + */ + fun cleanup() { + uploadBackend.submit { // In-progress uploads switch from the executor to the MC thread, so we need to follow them if we want // to make sure they're all done running. UMinecraft.getMinecraft().executor.execute { @@ -363,57 +409,11 @@ abstract class AsyncTextureManagerBase : AsyncTextureManager { complete.clear() } } - destroyContext() } - singletonExecutor.shutdown() + uploadBackendRefCounted.release { it.cleanup() } } companion object { - - private val nextThreadNumber = AtomicInteger(1) + private val uploadBackendRefCounted = RefCounted() } - -} - -class NotActuallyAsyncTextureManager : AsyncTextureManager { - private val complete = mutableMapOf() - - override fun upload(path: ScreenshotId, texture: () -> Pair) { - val (pixelBufferTexture, resourceLocation) = texture() - - Minecraft.getMinecraft().textureManager.loadTexture( - resourceLocation, - pixelBufferTexture - ) - complete[path] = resourceLocation - } - - override fun getFinished(): Set { - return complete.keys.toSet().also { complete.clear() } - } - - override fun cleanup() { - complete.forEach { (_, resourceLocation) -> - Minecraft.getMinecraft().textureManager.deleteTexture(resourceLocation) - } - complete.clear() - } -} - -interface AsyncTextureManager { - /** - * Schedules the [texture] function to be called on a worker thread. - * The texture object is then loaded by the Minecraft texture manager on the main thread - */ - fun upload(path: ScreenshotId, texture: () -> Pair) - - /** - * Returns the list of paths that had their textures uploaded since the last call to getFinished() - */ - fun getFinished(): Set - - /** - * Called to clean the context and shut down the worker thread - */ - fun cleanup() } diff --git a/src/main/kotlin/gg/essential/gui/sps/InviteFriendsModal.kt b/src/main/kotlin/gg/essential/gui/sps/InviteFriendsModal.kt index 50f40d6..058916f 100644 --- a/src/main/kotlin/gg/essential/gui/sps/InviteFriendsModal.kt +++ b/src/main/kotlin/gg/essential/gui/sps/InviteFriendsModal.kt @@ -30,7 +30,6 @@ import gg.essential.gui.common.shadow.EssentialUIText import gg.essential.gui.common.shadow.EssentialUIWrappedText import gg.essential.gui.common.shadow.ShadowEffect import gg.essential.gui.elementa.state.v2.* -import gg.essential.gui.elementa.state.v2.combinators.map import gg.essential.gui.layoutdsl.* import gg.essential.gui.modals.select.SelectModal import gg.essential.gui.modals.select.offlinePlayers @@ -348,8 +347,11 @@ object InviteFriendsModal { return selectModal(modalManager, "Invite Friends") { fun LayoutScope.customPlayerEntry(selected: MutableState, uuid: UUID) { + val onlineState = connectionManager.spsManager.getOnlineState(uuid) val reInviteEnabled = getReInviteEnabledState(uuid) - val reInviteVisible = selected.map { it && worldSummary == null } + val reInviteVisible = memo { + selected() && worldSummary == null && !onlineState() + } fun LayoutScope.reinviteButton(modifier: Modifier = Modifier) { iconButton( diff --git a/src/main/kotlin/gg/essential/gui/sps/categories/PlayersAndPermissionsCategory.kt b/src/main/kotlin/gg/essential/gui/sps/categories/PlayersAndPermissionsCategory.kt index d81a684..b709cce 100644 --- a/src/main/kotlin/gg/essential/gui/sps/categories/PlayersAndPermissionsCategory.kt +++ b/src/main/kotlin/gg/essential/gui/sps/categories/PlayersAndPermissionsCategory.kt @@ -19,7 +19,7 @@ import gg.essential.elementa.state.State import gg.essential.gui.EssentialPalette import gg.essential.gui.common.IconButton import gg.essential.gui.common.Spacer -import gg.essential.gui.common.WeakState +import gg.essential.gui.elementa.state.v2.onChange import gg.essential.gui.sps.InviteFriendsModal import gg.essential.gui.sps.options.SettingInformation import gg.essential.gui.sps.options.SpsOption @@ -28,6 +28,7 @@ import gg.essential.gui.util.hoveredState import gg.essential.network.connectionmanager.sps.SPSSessionSource import gg.essential.util.GuiUtil import gg.essential.vigilance.utils.onLeftClick +import gg.essential.gui.elementa.state.v2.State as StateV2 class PlayersAndPermissionsCategory( private val cheatsEnabled: State, @@ -37,7 +38,7 @@ class PlayersAndPermissionsCategory( ) { // Stored to keep a strong reference so the garbage collector doesn't delete our state listeners - private val stateListeners = mutableListOf>() + private val stateListeners = mutableListOf>() init { populate() @@ -55,7 +56,7 @@ class PlayersAndPermissionsCategory( // Host uuid == UUIDUtil.getClientUUID() -> 0 // Online - spsManager.getOnlineState(uuid).get() -> 1 + spsManager.getOnlineState(uuid).getUntracked() -> 1 // Invited else -> 2 } @@ -82,8 +83,8 @@ class PlayersAndPermissionsCategory( stateListeners.add(spsManager.getOnlineState(it)) } - stateListeners.forEach { weakState -> - weakState.onSetValue { sort() } + stateListeners.forEach { state -> + state.onChange(this) { sort() } } sort() diff --git a/src/main/kotlin/gg/essential/gui/sps/options/SpsOption.kt b/src/main/kotlin/gg/essential/gui/sps/options/SpsOption.kt index 6f7db74..3edf049 100644 --- a/src/main/kotlin/gg/essential/gui/sps/options/SpsOption.kt +++ b/src/main/kotlin/gg/essential/gui/sps/options/SpsOption.kt @@ -30,6 +30,7 @@ import gg.essential.gui.common.shadow.EssentialUIWrappedText import gg.essential.gui.common.shadow.ShadowIcon import gg.essential.gui.elementa.state.v2.* import gg.essential.gui.elementa.state.v2.ListState +import gg.essential.gui.elementa.state.v2.combinators.map import gg.essential.gui.util.hoveredState import gg.essential.mixins.transformers.server.integrated.LanConnectionsAccessor import gg.essential.universal.USound @@ -183,7 +184,7 @@ class SpsOption( y = 2.pixels } childOf playerInfo - val onSession = Essential.getInstance().connectionManager.spsManager.getOnlineState(information.uuid) + val onSession = Essential.getInstance().connectionManager.spsManager.getOnlineState(information.uuid).toV1(this) val descriptionText = if (information.uuid == UUIDUtil.getClientUUID()) { BasicState("Host") @@ -309,7 +310,7 @@ class SpsOption( fun removePlayerFromSession() { spsManager.updateInvitedUsers(spsManager.invitedUsers - player.uuid) // Updating the invited users is not sufficient to kick this player if the privacy is set to friends - if (onlineState.get() && spsManager.localSession!!.privacy == UPnPPrivacy.FRIENDS) { + if (onlineState.getUntracked() && spsManager.localSession!!.privacy == UPnPPrivacy.FRIENDS) { val integratedServer = Minecraft.getMinecraft().integratedServer ?: return integratedServer.executor.execute { @@ -349,7 +350,7 @@ class SpsOption( } else { "Cancel invite" } - }) + }.toV1(this)) }.onLeftClick { if (onlineState.get()) { UUIDUtil.getName(player.uuid).thenAcceptOnMainThread { username -> diff --git a/src/main/kotlin/gg/essential/gui/wardrobe/EmoteWheelPage.kt b/src/main/kotlin/gg/essential/gui/wardrobe/EmoteWheelPage.kt index 7cd714f..e3a80ba 100644 --- a/src/main/kotlin/gg/essential/gui/wardrobe/EmoteWheelPage.kt +++ b/src/main/kotlin/gg/essential/gui/wardrobe/EmoteWheelPage.kt @@ -185,18 +185,18 @@ class EmoteWheelPage(private val state: WardrobeState) : UIContainer() { val target = state.draggingOntoEmoteSlot.get() if (target != null) { if (target == -1) { - state.emoteWheel.set(index, null) + state.emoteWheelManager.setEmote(index, null) } else { - val sourceEmote = state.emoteWheel.get()[index] - val targetEmote = state.emoteWheel.get()[target] - state.emoteWheel.set(index, targetEmote) - state.emoteWheel.set(target, sourceEmote) + val sourceEmote = state.emoteWheel.getUntracked()[index] + val targetEmote = state.emoteWheel.getUntracked()[target] + state.emoteWheelManager.setEmote(index, targetEmote) + state.emoteWheelManager.setEmote(target, sourceEmote) } } else if (mayBeLeftClick) { - state.emoteWheel.get()[index]?.let { + state.emoteWheel.getUntracked()[index]?.let { USound.playButtonPress() } - state.emoteWheel.set(index, null) + state.emoteWheelManager.setEmote(index, null) } state.draggingEmoteSlot.set(null) diff --git a/src/main/kotlin/gg/essential/gui/wardrobe/Item.kt b/src/main/kotlin/gg/essential/gui/wardrobe/Item.kt index 89f67ae..57b1c2d 100644 --- a/src/main/kotlin/gg/essential/gui/wardrobe/Item.kt +++ b/src/main/kotlin/gg/essential/gui/wardrobe/Item.kt @@ -125,6 +125,7 @@ sealed interface Item { override val name: String, override val tier: Tier, val discountPercent: Float, + val rotateOnPreview: Boolean, val skin: CosmeticBundle.Skin, val cosmetics: Map, val settings: Map>, @@ -194,6 +195,7 @@ sealed interface Item { name, tier.toItemTier(), discountPercent, + rotateOnPreview, skin, cosmetics, settings diff --git a/src/main/kotlin/gg/essential/gui/wardrobe/Wardrobe.kt b/src/main/kotlin/gg/essential/gui/wardrobe/Wardrobe.kt index 246e03c..e896015 100644 --- a/src/main/kotlin/gg/essential/gui/wardrobe/Wardrobe.kt +++ b/src/main/kotlin/gg/essential/gui/wardrobe/Wardrobe.kt @@ -66,6 +66,7 @@ class Wardrobe( val cosmeticsManager = connectionManager.cosmeticsManager val skinsManager = connectionManager.skinsManager val outfitManager = connectionManager.outfitManager + val emoteWheelManager = connectionManager.emoteWheelManager val coinsManager = connectionManager.coinsManager private val guiScaleState = mutableStateOf(newGuiScale) @@ -76,6 +77,7 @@ class Wardrobe( window, cosmeticsManager, skinsManager, + emoteWheelManager, coinsManager, guiScaleState, ) @@ -339,13 +341,13 @@ class Wardrobe( Essential.getInstance().skinManager.flushChanges(false) } outfitManager.flushSelectedOutfit(false) - cosmeticsManager.flushSelectedEmoteWheel(false) + emoteWheelManager.flushSelectedEmoteWheel(false) val outfit = outfitManager.getSelectedOutfit() if (outfit != null) { val skin = connectionManager.skinsManager.getSkin(outfit.skinId ?: "").get() if (skin != null) { - connectionManager.skinsManager.updateLastUsedAtState(skin) + connectionManager.skinsManager.updateLastUsedAtState(skin.id) } } @@ -371,7 +373,7 @@ class Wardrobe( val emotes = state.emoteWheel.get().toMutableList() // Copy list to avoid concurrent modification emotes.forEachIndexed { index, s -> if (s != null && s !in state.cosmeticsManager.unlockedCosmetics.get()) { - state.emoteWheel.set(index, null) + state.emoteWheelManager.setEmote(index, null) } } restorePreviousScreen() diff --git a/src/main/kotlin/gg/essential/gui/wardrobe/WardrobeState.kt b/src/main/kotlin/gg/essential/gui/wardrobe/WardrobeState.kt index 4ac4e1f..5bcf65a 100644 --- a/src/main/kotlin/gg/essential/gui/wardrobe/WardrobeState.kt +++ b/src/main/kotlin/gg/essential/gui/wardrobe/WardrobeState.kt @@ -36,7 +36,6 @@ import gg.essential.gui.elementa.state.v2.filterNotNull import gg.essential.gui.elementa.state.v2.mapEach import gg.essential.gui.elementa.state.v2.mapEachNotNull import gg.essential.gui.elementa.state.v2.memo -import gg.essential.gui.elementa.state.v2.mutableListStateOf import gg.essential.gui.elementa.state.v2.mutableStateOf import gg.essential.gui.elementa.state.v2.onChange import gg.essential.gui.elementa.state.v2.set @@ -45,7 +44,6 @@ import gg.essential.gui.elementa.state.v2.stateBy import gg.essential.gui.elementa.state.v2.stateOf import gg.essential.gui.elementa.state.v2.toListState import gg.essential.gui.elementa.state.v2.zipWithEachElement -import gg.essential.gui.emotes.EmoteWheel import gg.essential.gui.util.layoutSafePollingState import gg.essential.gui.wardrobe.Item.Companion.toItem import gg.essential.gui.wardrobe.components.handleBundleLeftClick @@ -74,6 +72,7 @@ import gg.essential.gui.util.pollingStateV2 import gg.essential.mod.cosmetics.featured.FeaturedItem import gg.essential.mod.cosmetics.settings.CosmeticSettings import gg.essential.mod.cosmetics.settings.setting +import gg.essential.network.connectionmanager.cosmetics.EmoteWheelManager import gg.essential.universal.UResolution import java.util.concurrent.TimeUnit @@ -83,6 +82,7 @@ class WardrobeState( component: UIComponent, val cosmeticsManager: CosmeticsManager, val skinsManager: SkinsManager, + val emoteWheelManager: EmoteWheelManager, val coinsManager: CoinsManager, private val guiScale: State, ) { @@ -205,7 +205,9 @@ class WardrobeState( Item.OutfitItem(outfit.id, outfit.name, skinId, skin, outfit.equippedCosmetics, outfit.cosmeticSettings, outfit.createdAt, outfit.favoritedSince) } } - val skinItems = skinsManager.skinsOrdered + val skinItems = skinsManager.skinsOrdered.mapEach { skin -> + Item.SkinItem(skin.id, skin.name, skin.skin, skin.createdAt, skin.lastUsedAt, skin.favoritedSince) + } private fun ListState.filteredBySearch() = zipWithEachElement(search) { item, search -> @@ -327,12 +329,7 @@ class WardrobeState( /** Slot that a drag&drop is currently hovering on top of. `-1` for "Remove". */ val draggingOntoEmoteSlot = mutableStateOf(null) - val emoteWheel = mutableListStateOf(*arrayOfNulls(EmoteWheel.SLOTS)) - .apply { - cosmeticsManager.savedEmotes.forEachIndexed { index, element -> set(index, element) } - onSetValue(component) { cosmeticsManager.savedEmotes = it } - unlockedCosmetics.onSetValue(component) { cosmeticsManager.savedEmotes = this.get() } - } + val emoteWheel = emoteWheelManager.selectedEmoteWheelSlots val draggingOntoOccupiedEmoteSlot = draggingOntoEmoteSlot.zip(emoteWheel).map { (slot, wheel) -> @@ -340,7 +337,6 @@ class WardrobeState( } val equippedOutfitId = outfitManager.selectedOutfitId - val equippedEmoteSlot = mutableStateOf(cosmeticsManager.emoteWheels.indexOfFirst { it.isSelected }) val equippedOutfitItem: State = stateBy { val equippedId = equippedOutfitId() ?: return@stateBy null @@ -458,6 +454,8 @@ class WardrobeState( val purchaseConfirmationEmoteId = "ESSENTIAL_PURCHASE_CONFIRMATION" + val wardrobeOpenTime = System.currentTimeMillis() + init { // Register purchase confirmation emote cosmeticsManager.infraCosmeticsData.requestCosmeticsIfMissing(listOf(purchaseConfirmationEmoteId)) @@ -506,14 +504,13 @@ class WardrobeState( } fun changeEmoteWheel(offset: Int) { - equippedEmoteSlot.set { cosmeticsManager.shiftSelectedEmoteWheel(offset) } - emoteWheel.setAll(cosmeticsManager.savedEmotes) + emoteWheelManager.shiftSelectedEmoteWheel(offset) } fun setFavorite(item: Item, favorite: Boolean) { when (item) { is Item.OutfitItem -> outfitManager.setFavorite(item.id, favorite) - is Item.SkinItem -> skinsManager.setFavoriteState(item, favorite) + is Item.SkinItem -> skinsManager.setFavoriteState(item.id, favorite) else -> {} } } @@ -701,7 +698,7 @@ class WardrobeState( val displayName: String, comparator: Comparator, ) : Comparator by comparator { - Default("All Items", sortByOrderInCollection.then(sortBySortWeight).then(sortByPriority)), + Default("All Items", sortByOrderInCollection.then(sortBySortWeight).then(sortByPriority).then(sortByName)), Alphabetical("A-Z", sortByName.then(Default)), Owned("Owned Only", Default), Price("Price", sortByPrice.then(Default)), diff --git a/src/main/kotlin/gg/essential/gui/wardrobe/components/cosmeticItem.kt b/src/main/kotlin/gg/essential/gui/wardrobe/components/cosmeticItem.kt index b5fb1f9..c4809f1 100644 --- a/src/main/kotlin/gg/essential/gui/wardrobe/components/cosmeticItem.kt +++ b/src/main/kotlin/gg/essential/gui/wardrobe/components/cosmeticItem.kt @@ -345,7 +345,7 @@ fun LayoutScope.cosmeticItem(item: Item, category: WardrobeCategory, state: Ward claimFreeItemNow(item, state) } - state.emoteWheel.set(target, item.cosmetic.id) + state.emoteWheelManager.setEmote(target, item.cosmetic.id) USound.playButtonPress() } diff --git a/src/main/kotlin/gg/essential/gui/wardrobe/components/cosmeticOrEmoteItemFunctions.kt b/src/main/kotlin/gg/essential/gui/wardrobe/components/cosmeticOrEmoteItemFunctions.kt index da9b586..89da683 100644 --- a/src/main/kotlin/gg/essential/gui/wardrobe/components/cosmeticOrEmoteItemFunctions.kt +++ b/src/main/kotlin/gg/essential/gui/wardrobe/components/cosmeticOrEmoteItemFunctions.kt @@ -85,20 +85,20 @@ fun handleCosmeticOrEmoteLeftClick(item: Item.CosmeticOrEmote, category: Wardrob if (wardrobeState.inEmoteWheel.get()) { val emoteWheel = wardrobeState.emoteWheel - val existingIndex = emoteWheel.get().indexOf(cosmetic.id) + val existingIndex = emoteWheel.getUntracked().indexOf(cosmetic.id) if (existingIndex != -1) { if (startedInEmoteWheel && !bundleWasSelected && !emoteWasSelected) { // Only remove the emote if the emote wheel preview was open when the emote was clicked and a bundle was not selected - emoteWheel.set(existingIndex, null) + wardrobeState.emoteWheelManager.setEmote(existingIndex, null) // Remove duplicates as well - while (emoteWheel.get().indexOf(cosmetic.id) != -1) { - emoteWheel.set(emoteWheel.get().indexOf(cosmetic.id), null) + while (emoteWheel.getUntracked().indexOf(cosmetic.id) != -1) { + wardrobeState.emoteWheelManager.setEmote(emoteWheel.getUntracked().indexOf(cosmetic.id), null) } } } else { - val emptyIndex = emoteWheel.get().indexOfFirst { it == null } + val emptyIndex = emoteWheel.getUntracked().indexOfFirst { it == null } if (emptyIndex != -1) { - emoteWheel.set(emptyIndex, cosmetic.id) + wardrobeState.emoteWheelManager.setEmote(emptyIndex, cosmetic.id) } else { Notifications.warning("Emote wheel is full.", "") } @@ -378,10 +378,6 @@ fun claimFreeItemNow(item: Item.CosmeticOrEmote, wardrobeState: WardrobeState) { wardrobeState.unlockedCosmetics.set { it - cosmetic.id } Notifications.error("Error", "Failed to claim item.") } else { - val slot = cosmetic.type.slot - if (slot == CosmeticSlot.EMOTE) { - cosmeticsManager.savedEmotes = wardrobeState.emoteWheel.get() - } sendUnlockedToast(cosmetic.id, wardrobeState.selectedPreviewingEquippedSettings.map { it[item.id] ?: listOf() }) EssentialSoundManager.playPurchaseConfirmationSound() } diff --git a/src/main/kotlin/gg/essential/gui/wardrobe/components/gifting.kt b/src/main/kotlin/gg/essential/gui/wardrobe/components/gifting.kt index c1a7a06..ca97c3f 100644 --- a/src/main/kotlin/gg/essential/gui/wardrobe/components/gifting.kt +++ b/src/main/kotlin/gg/essential/gui/wardrobe/components/gifting.kt @@ -41,6 +41,7 @@ import gg.essential.gui.modals.select.users import gg.essential.gui.notification.Notifications import gg.essential.gui.notification.content.CosmeticPreviewToastComponent import gg.essential.gui.wardrobe.Item +import gg.essential.gui.wardrobe.ItemId import gg.essential.gui.wardrobe.Wardrobe import gg.essential.gui.wardrobe.WardrobeCategory import gg.essential.gui.wardrobe.WardrobeState @@ -184,14 +185,14 @@ fun openGiftModal(item: Item.CosmeticOrEmote, state: WardrobeState) { } } -fun openWardrobeWithHighlight(item: Item) { +fun openWardrobeWithHighlight(itemId: ItemId) { val openedScreen = GuiUtil.openedScreen() if (openedScreen is Wardrobe) { - openedScreen.state.highlightItem.set(item.itemId) + openedScreen.state.highlightItem.set(itemId) } else { GuiUtil.openScreen { // Change initial category to stop the highlighted item highlighting on the featured page - Wardrobe(WardrobeCategory.Cosmetics).apply { state.highlightItem.set(item.itemId) } + Wardrobe(WardrobeCategory.Cosmetics).apply { state.highlightItem.set(itemId) } } } } @@ -246,7 +247,7 @@ fun showGiftSentToast(cosmetic: Cosmetic, username: String) { fun showGiftReceivedToast(cosmeticId: String, uuid: UUID, username: String) { val cosmetic = Essential.getInstance().connectionManager.cosmeticsManager.getCosmetic(cosmeticId) ?: return Notifications.push(username, "", 4f, { - openWardrobeWithHighlight(Item.CosmeticOrEmote(cosmetic)) + openWardrobeWithHighlight(ItemId.CosmeticOrEmote(cosmetic.id)) }) { withCustomComponent(Slot.ICON, CachedAvatarImage.create(uuid)) withCustomComponent(Slot.SMALL_PREVIEW, CosmeticPreviewToastComponent(cosmetic)) diff --git a/src/main/kotlin/gg/essential/gui/wardrobe/components/outfitItemFunctions.kt b/src/main/kotlin/gg/essential/gui/wardrobe/components/outfitItemFunctions.kt index b9bdd4d..b7c7473 100644 --- a/src/main/kotlin/gg/essential/gui/wardrobe/components/outfitItemFunctions.kt +++ b/src/main/kotlin/gg/essential/gui/wardrobe/components/outfitItemFunctions.kt @@ -33,7 +33,7 @@ fun displayOutfitOptions(item: Item.OutfitItem, wardrobeState: WardrobeState, ev val options = mutableListOf() options.add( ContextOptionMenu.Option("Rename", image = EssentialPalette.PENCIL_7x7) { - GuiUtil.pushModal { manager -> + GuiUtil.pushModal { manager -> CancelableInputModal(manager, "Outfit Name", maxLength = 22, initialText = item.name) .configure { titleText = "Rename Outfit" @@ -92,7 +92,7 @@ fun displayOutfitOptions(item: Item.OutfitItem, wardrobeState: WardrobeState, ev image = EssentialPalette.TRASH_9X, hoveredColor = EssentialPalette.TEXT_WARNING ) { - GuiUtil.pushModal { manager -> + GuiUtil.pushModal { manager -> DangerConfirmationEssentialModal(manager, "Delete", true).configure { titleText = "Are you sure you want to delete ${item.name}?" }.onPrimaryAction { @@ -107,7 +107,7 @@ fun displayOutfitOptions(item: Item.OutfitItem, wardrobeState: WardrobeState, ev if (cosmeticsDataWithChanges != null) { options.add( ContextOptionMenu.Option("Create bundle", image = EssentialPalette.PLUS_7X) { - GuiUtil.pushModal { manager -> + GuiUtil.pushModal { manager -> CancelableInputModal(manager, "Bundle id").configure { titleText = "Create New Bundle" contentText = "Enter the id for the new bundle." @@ -122,6 +122,7 @@ fun displayOutfitOptions(item: Item.OutfitItem, wardrobeState: WardrobeState, ev item.name, CosmeticTier.COMMON, 0f, + false, CosmeticBundle.Skin(item.skin, item.name), item.cosmetics, item.settings, diff --git a/src/main/kotlin/gg/essential/gui/wardrobe/components/previewWindow.kt b/src/main/kotlin/gg/essential/gui/wardrobe/components/previewWindow.kt index 419b2f6..bb46707 100644 --- a/src/main/kotlin/gg/essential/gui/wardrobe/components/previewWindow.kt +++ b/src/main/kotlin/gg/essential/gui/wardrobe/components/previewWindow.kt @@ -110,7 +110,7 @@ fun LayoutScope.previewWindowTitleBar(state: WardrobeState, modifier: Modifier) val equippedOutfitItem = state.equippedOutfitItem() when { selectedBundle != null -> selectedBundle.name to false - state.inEmoteWheel() -> "Wheel #${state.equippedEmoteSlot() + 1}" to false + state.inEmoteWheel() -> "Wheel #${state.emoteWheelManager.selectedEmoteWheelIndex() + 1}" to false selectedEmote != null -> selectedEmote.name to false equippedOutfitItem != null -> equippedOutfitItem.name to true else -> "Unknown Outfit" to false diff --git a/src/main/kotlin/gg/essential/gui/wardrobe/components/skinItemFunctions.kt b/src/main/kotlin/gg/essential/gui/wardrobe/components/skinItemFunctions.kt index 482ec61..ba909d9 100644 --- a/src/main/kotlin/gg/essential/gui/wardrobe/components/skinItemFunctions.kt +++ b/src/main/kotlin/gg/essential/gui/wardrobe/components/skinItemFunctions.kt @@ -27,7 +27,7 @@ import gg.essential.util.* fun handleSkinLeftClick(skin: Item.SkinItem, wardrobeState: WardrobeState) { USound.playButtonPress() wardrobeState.selectedItem.set(skin) - wardrobeState.skinsManager.selectSkin(skin) + wardrobeState.skinsManager.selectSkin(skin.id) } fun handleSkinRightClick(skin: Item.SkinItem, wardrobeState: WardrobeState, event: UIClickEvent) { @@ -43,14 +43,14 @@ fun handleSkinRightClick(skin: Item.SkinItem, wardrobeState: WardrobeState, even GuiUtil.pushModal { createSkinShareModal(it, skin) } }, ContextOptionMenu.Option(if (skin.isFavorite) "Remove Favorite" else "Favorite", EssentialPalette.HEART_7X6) { - wardrobeState.skinsManager.setFavoriteState(skin, !skin.isFavorite) + wardrobeState.skinsManager.setFavoriteState(skin.id, !skin.isFavorite) }, ) if (wardrobeState.skinsManager.skins.get().size > 1) { options.add(ContextOptionMenu.Divider) options.add(ContextOptionMenu.Option("Delete", EssentialPalette.TRASH_9X, hoveredColor = EssentialPalette.TEXT_WARNING) { - wardrobeState.skinsManager.openDeleteSkinModal(skin) + wardrobeState.skinsManager.openDeleteSkinModal(skin.id) }) } diff --git a/src/main/kotlin/gg/essential/gui/wardrobe/configuration/ConfigurationType.kt b/src/main/kotlin/gg/essential/gui/wardrobe/configuration/ConfigurationType.kt index edbb1a7..0bce91a 100644 --- a/src/main/kotlin/gg/essential/gui/wardrobe/configuration/ConfigurationType.kt +++ b/src/main/kotlin/gg/essential/gui/wardrobe/configuration/ConfigurationType.kt @@ -19,7 +19,6 @@ import gg.essential.gui.elementa.state.v2.* import gg.essential.gui.overlay.ModalManager import gg.essential.gui.wardrobe.WardrobeState import gg.essential.mod.Model -import gg.essential.mod.Skin import gg.essential.mod.cosmetics.CosmeticBundle import gg.essential.mod.cosmetics.CosmeticSlot import gg.essential.mod.cosmetics.CosmeticTier @@ -137,6 +136,7 @@ class ConfigurationType private constructor( "Bundle name", CosmeticTier.COMMON, 0f, + false, CosmeticBundle.Skin("bff1570fdf623153e6b4a4d2ca97559b471f1ec776584ceec2ebb8bf0b7ba504", Model.ALEX), // A default skin I use for my alt, just so it's not empty :) mapOf(), mapOf(), diff --git a/src/main/kotlin/gg/essential/gui/wardrobe/configuration/ConfigurationUtils.kt b/src/main/kotlin/gg/essential/gui/wardrobe/configuration/ConfigurationUtils.kt index ea9565f..0fa53b3 100644 --- a/src/main/kotlin/gg/essential/gui/wardrobe/configuration/ConfigurationUtils.kt +++ b/src/main/kotlin/gg/essential/gui/wardrobe/configuration/ConfigurationUtils.kt @@ -152,6 +152,23 @@ object ConfigurationUtils { return input } + fun LayoutScope.labeledBooleanInputRow( + label: String, + initialValue: Boolean, + inputModifier: Modifier = Modifier, + horizontalArrangement: Arrangement = Arrangement.SpaceBetween, + onSetValue: (Boolean) -> Unit + ) { + labeledListInputRow( + label, + initialValue, + listStateOf(EssentialDropDown.Option("True", true), EssentialDropDown.Option("False", false)), + inputModifier, + horizontalArrangement, + onSetValue + ) + } + fun LayoutScope.labeledIntInputRow( label: String, state: MutableState, diff --git a/src/main/kotlin/gg/essential/gui/wardrobe/configuration/CosmeticBundleConfiguration.kt b/src/main/kotlin/gg/essential/gui/wardrobe/configuration/CosmeticBundleConfiguration.kt index 49ca3ce..9d82ee8 100644 --- a/src/main/kotlin/gg/essential/gui/wardrobe/configuration/CosmeticBundleConfiguration.kt +++ b/src/main/kotlin/gg/essential/gui/wardrobe/configuration/CosmeticBundleConfiguration.kt @@ -18,6 +18,7 @@ import gg.essential.gui.common.state import gg.essential.gui.elementa.state.v2.* import gg.essential.gui.layoutdsl.* import gg.essential.gui.wardrobe.WardrobeState +import gg.essential.gui.wardrobe.configuration.ConfigurationUtils.labeledBooleanInputRow import gg.essential.gui.wardrobe.configuration.ConfigurationUtils.labeledEnumInputRow import gg.essential.gui.wardrobe.configuration.ConfigurationUtils.labeledFloatInputRow import gg.essential.gui.wardrobe.configuration.ConfigurationUtils.labeledNullableStringInputRow @@ -41,6 +42,7 @@ class CosmeticBundleConfiguration( labeledStringInputRow("Name:", mutableStateOf(bundle.name)).state.onSetValue(stateScope) { bundle.update(bundle.copy(name = it)) } labeledEnumInputRow("Tier:", bundle.tier) { bundle.update(bundle.copy(tier = it)) } labeledFloatInputRow("Discount %:", mutableStateOf(bundle.discountPercent)).state.onSetValue(stateScope) { bundle.update(bundle.copy(discountPercent = it)) } + labeledBooleanInputRow("Rotate on Preview:", bundle.rotateOnPreview) { bundle.update(bundle.copy(rotateOnPreview = it)) } labeledEnumInputRow("Skin model:", skin.model) { bundle.update(bundle.copy(skin = bundle.skin.copy(model = it))) } labeledStringInputRow("Skin hash:", mutableStateOf(skin.hash), Modifier.fillRemainingWidth(), Arrangement.spacedBy(10f)).state.onSetValue(stateScope) { bundle.update(bundle.copy(skin = bundle.skin.copy(hash = it))) } labeledNullableStringInputRow("Skin name:", mutableStateOf(skin.name)).state.onSetValue(stateScope) { bundle.update(bundle.copy(skin = bundle.skin.copy(name = it))) } @@ -54,12 +56,25 @@ class CosmeticBundleConfiguration( { it ?: "" }, { if (it.isBlank()) null else (cosmeticsDataWithChanges.getCosmetic(it)?.id ?: throw StateTextInput.ParseException()) } ) - cosmeticState.onSetValue(stateScope) { + cosmeticState.onSetValue(stateScope) { cosmeticId -> bundle.update( - if (it == null) { + if (cosmeticId == null) { bundle.copy(cosmetics = bundle.cosmetics - slot, settings = if (initialId == null) bundle.settings else bundle.settings - initialId) } else { - bundle.copy(cosmetics = bundle.cosmetics + (slot to it)) + val newCosmetics = bundle.cosmetics.toMutableMap() + // All slots that the equipped cosmetic is mutually exclusive with + for (mutuallyExclusiveSlot in cosmeticsDataWithChanges.getCosmetic(cosmeticId)?.mutuallyExclusiveWith ?: emptySet()) { + newCosmetics.remove(mutuallyExclusiveSlot) + } + // All other slots with an equipped cosmetic that is mutually exclusive with the newly equipped slot + for ((equippedSlot, equippedCosmeticId) in newCosmetics.entries) { + if (cosmeticsDataWithChanges.getCosmetic(equippedCosmeticId)?.mutuallyExclusiveWith?.contains(slot) == true) { + newCosmetics.remove(equippedSlot) + } + } + + newCosmetics[slot] = cosmeticId + bundle.copy(cosmetics = newCosmetics, settings = bundle.settings - (bundle.settings.keys - newCosmetics.values)) } ) } diff --git a/src/main/kotlin/gg/essential/gui/wardrobe/configuration/CosmeticConfiguration.kt b/src/main/kotlin/gg/essential/gui/wardrobe/configuration/CosmeticConfiguration.kt index afe7ffa..f6ec588 100644 --- a/src/main/kotlin/gg/essential/gui/wardrobe/configuration/CosmeticConfiguration.kt +++ b/src/main/kotlin/gg/essential/gui/wardrobe/configuration/CosmeticConfiguration.kt @@ -169,6 +169,7 @@ class CosmeticConfiguration( CosmeticPropertyType.REQUIRES_UNLOCK_ACTION -> RequiresUnlockActionConfiguration(cosmeticsDataWithChanges, cosmetic) CosmeticPropertyType.VARIANTS -> VariantsPropertyConfiguration(cosmeticsDataWithChanges, cosmetic) CosmeticPropertyType.DEFAULT_SIDE -> DefaultSidePropertyConfiguration(cosmeticsDataWithChanges, cosmetic) + CosmeticPropertyType.MUTUALLY_EXCLUSIVE -> MutuallyExclusivePropertyConfiguration(cosmeticsDataWithChanges, cosmetic) }).let { it() } diff --git a/src/main/kotlin/gg/essential/gui/wardrobe/configuration/cosmetic/properties/MutuallyExclusivePropertyConfiguration.kt b/src/main/kotlin/gg/essential/gui/wardrobe/configuration/cosmetic/properties/MutuallyExclusivePropertyConfiguration.kt new file mode 100644 index 0000000..a817824 --- /dev/null +++ b/src/main/kotlin/gg/essential/gui/wardrobe/configuration/cosmetic/properties/MutuallyExclusivePropertyConfiguration.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.gui.wardrobe.configuration.cosmetic.properties + +import gg.essential.gui.layoutdsl.* +import gg.essential.gui.wardrobe.configuration.ConfigurationUtils.labeledRow +import gg.essential.mod.cosmetics.CosmeticSlot +import gg.essential.mod.cosmetics.settings.CosmeticProperty +import gg.essential.network.connectionmanager.cosmetics.* +import gg.essential.network.cosmetics.Cosmetic + +class MutuallyExclusivePropertyConfiguration( + cosmeticsDataWithChanges: CosmeticsDataWithChanges, + cosmetic: Cosmetic, +) : SingletonPropertyConfiguration( + CosmeticProperty.MutuallyExclusive::class.java, + cosmeticsDataWithChanges, + cosmetic +) { + + override fun LayoutScope.layout(property: CosmeticProperty.MutuallyExclusive) { + val slots = property.data.slots + for (slot in CosmeticSlot.values()) { + labeledRow(slot.id) { checkbox(slots.contains(slot)) { property.update(property.copy(data = property.data.copy(slots = if (it) slots + slot else slots - slot))) } } + } + } + +} diff --git a/src/main/kotlin/gg/essential/gui/wardrobe/modals/CoinsPurchaseModal.kt b/src/main/kotlin/gg/essential/gui/wardrobe/modals/CoinsPurchaseModal.kt index ae9d68c..a716f4e 100644 --- a/src/main/kotlin/gg/essential/gui/wardrobe/modals/CoinsPurchaseModal.kt +++ b/src/main/kotlin/gg/essential/gui/wardrobe/modals/CoinsPurchaseModal.kt @@ -24,7 +24,6 @@ import gg.essential.gui.common.and import gg.essential.gui.common.input.UITextInput import gg.essential.gui.common.input.essentialInput import gg.essential.gui.common.modal.Modal -import gg.essential.gui.common.modal.OpenLinkModal import gg.essential.gui.elementa.state.v2.* import gg.essential.gui.elementa.state.v2.combinators.* import gg.essential.gui.elementa.state.v2.stateBy @@ -39,6 +38,7 @@ import gg.essential.network.connectionmanager.coins.CoinBundle import gg.essential.universal.USound import gg.essential.util.* import gg.essential.vigilance.utils.onLeftClick +import kotlin.math.max class CoinsPurchaseModal( modalManager: ModalManager, @@ -98,7 +98,13 @@ class CoinsPurchaseModal( ) fun LayoutScope.bundleBox(originalBundle: CoinBundle) { - val bundleState = coinsNeededState.map { missingCoins -> if (originalBundle.isExchangeBundle && missingCoins > 0) originalBundle.getBundleForNumberOfCoins(missingCoins) else originalBundle } + val bundleState = memo { + val missingCoins = coinsNeededState() + if (originalBundle.isExchangeBundle && missingCoins > 0) { + val minimumBundleSize = state.cosmeticsManager.wardrobeSettings.youNeedMinimumAmount() + originalBundle.getBundleForNumberOfCoins(max(minimumBundleSize, missingCoins)) + } else originalBundle + } bind(bundleState) { bundle -> val colorBackground = if (bundle.isHighlighted || bundle.isSpecificAmount) EssentialPalette.COINS_BLUE_BACKGROUND else EssentialPalette.COMPONENT_BACKGROUND_HIGHLIGHT val colorBackgroundHover = if (bundle.isHighlighted || bundle.isSpecificAmount) EssentialPalette.COINS_BLUE_BACKGROUND_HOVER else EssentialPalette.LIGHTEST_BACKGROUND @@ -168,7 +174,7 @@ class CoinsPurchaseModal( USound.playButtonPress() coinsManager.purchaseBundle(bundle) { uri -> close() - OpenLinkModal.browse(uri, true) + openInBrowser(uri) } } } diff --git a/src/main/kotlin/gg/essential/gui/wardrobe/modals/SkinModal.kt b/src/main/kotlin/gg/essential/gui/wardrobe/modals/SkinModal.kt index 60d8a8c..f35d881 100644 --- a/src/main/kotlin/gg/essential/gui/wardrobe/modals/SkinModal.kt +++ b/src/main/kotlin/gg/essential/gui/wardrobe/modals/SkinModal.kt @@ -36,6 +36,7 @@ import gg.essential.gui.overlay.ModalManager import gg.essential.gui.sendCheckmarkNotification import gg.essential.gui.skin.preprocessSkinImage import gg.essential.gui.wardrobe.Item +import gg.essential.gui.wardrobe.ItemId import gg.essential.gui.wardrobe.WardrobeState import gg.essential.gui.wardrobe.components.openWardrobeWithHighlight import gg.essential.handlers.GameProfileManager @@ -157,7 +158,7 @@ class SkinModal private constructor( Essential.getInstance().connectionManager.skinsManager.addSkin(name, updatedSkin, selectSkin).whenComplete { skin, throwable -> if (skin != null) { sendCheckmarkNotification("Skin has been added.") { - openWardrobeWithHighlight(skin) + openWardrobeWithHighlight(ItemId.SkinItem(skin.id)) } } else { Essential.logger.warn("Error adding skin!", throwable) @@ -170,10 +171,10 @@ class SkinModal private constructor( val skinsManager = Essential.getInstance().connectionManager.skinsManager // Because the update packets are separate, we have to do each one separately if (skin.name != name) { - skinsManager.renameSkin(skin, name) + skinsManager.renameSkin(skin.id, name) } if (skin.skin.model != model) { - skinsManager.setSkinModel(skin, model) + skinsManager.setSkinModel(skin.id, model) } } diff --git a/src/main/kotlin/gg/essential/handlers/EssentialGameRules.kt b/src/main/kotlin/gg/essential/handlers/EssentialGameRules.kt index 0c17740..f4792ed 100644 --- a/src/main/kotlin/gg/essential/handlers/EssentialGameRules.kt +++ b/src/main/kotlin/gg/essential/handlers/EssentialGameRules.kt @@ -14,6 +14,10 @@ package gg.essential.handlers import net.minecraft.server.MinecraftServer import net.minecraft.world.GameRules +//#if MC>=12102 +//$$ import net.minecraft.resource.featuretoggle.FeatureSet +//#endif + //#if MC>=11600 //$$ import gg.essential.mixins.transformers.server.GameRulesAccessor //$$ import gg.essential.mixins.transformers.server.GameRulesBooleanValueAccessor @@ -49,7 +53,12 @@ class EssentialGameRules { //#if MC>=11600 //$$ private fun ruleExists(name: String): Boolean { //$$ var exists = false - //$$ GameRules.visitAll(object : GameRules.IRuleEntryVisitor { + //#if MC>=12102 + //$$ // Note: FeatureSet value doesn't actually matter, `accept` will (at least currently) visit all gamerules + //$$ GameRules(FeatureSet.empty()).accept(object : GameRules.Visitor { + //#else + //$$ GameRules.visitAll(object : GameRules.IRuleEntryVisitor { + //#endif //$$ override fun ?> visit(key: GameRules.RuleKey, type: GameRules.RuleType) { //$$ if (key.name == name) exists = true //$$ } diff --git a/src/main/kotlin/gg/essential/handlers/OptionsScreenOverlay.kt b/src/main/kotlin/gg/essential/handlers/OptionsScreenOverlay.kt index f64133c..5a779ca 100644 --- a/src/main/kotlin/gg/essential/handlers/OptionsScreenOverlay.kt +++ b/src/main/kotlin/gg/essential/handlers/OptionsScreenOverlay.kt @@ -11,7 +11,7 @@ */ package gg.essential.handlers -import gg.essential.config.EssentialConfig +import gg.essential.config.McEssentialConfig import gg.essential.elementa.components.UIContainer import gg.essential.elementa.constraints.AspectConstraint import gg.essential.elementa.constraints.CenterConstraint @@ -69,7 +69,7 @@ class OptionsScreenOverlay { } childOf window val settingsButton by MenuButton(shouldBeRetextured = true) { - GuiUtil.openScreen { EssentialConfig.gui() } + GuiUtil.openScreen { McEssentialConfig.gui() } }.constrain { x = SiblingConstraint(4f) boundTo bottomRightButton y = 0.pixels boundTo bottomRightButton diff --git a/src/main/kotlin/gg/essential/handlers/PauseMenuDisplay.kt b/src/main/kotlin/gg/essential/handlers/PauseMenuDisplay.kt index 2d0bed2..d762fdb 100644 --- a/src/main/kotlin/gg/essential/handlers/PauseMenuDisplay.kt +++ b/src/main/kotlin/gg/essential/handlers/PauseMenuDisplay.kt @@ -180,16 +180,18 @@ class PauseMenuDisplay { if (AutoUpdate.updateAvailable.get() && !AutoUpdate.seenUpdateToast && !AutoUpdate.updateIgnored.get()) { fun showUpdateToast(message: String? = null) { var updateClicked = false - val updateButton = UIBlock(EssentialPalette.GREEN_BUTTON).onLeftClick { - GuiUtil.pushModal { manager -> UpdateAvailableModal(manager) } - it.stopPropagation() - } - updateButton.layout(Modifier.childBasedWidth(10f).childBasedHeight(4.5f).hoverColor(EssentialPalette.GREEN_BUTTON_HOVER).hoverScope()) { - text("Update", Modifier.alignBoth(Alignment.Center(true)).color(EssentialPalette.TEXT_HIGHLIGHT).shadow(EssentialPalette.TEXT_SHADOW)) + val updateButton = UIBlock().apply { + layout(Modifier.childBasedWidth(10f).childBasedHeight(4.5f) + .color(EssentialPalette.GREEN_BUTTON).hoverColor(EssentialPalette.GREEN_BUTTON_HOVER).hoverScope() + ) { + text("Update", Modifier.alignBoth(Alignment.Center(true)).color(EssentialPalette.TEXT_HIGHLIGHT).shadow(EssentialPalette.TEXT_SHADOW)) + } } - Notifications.pushPersistentToast(AutoUpdate.getNotificationTitle(false), message ?: " ", {}, { + Notifications.pushPersistentToast(AutoUpdate.getNotificationTitle(false), message ?: " ", { + GuiUtil.pushModal { manager -> UpdateAvailableModal(manager) } + }, { if (!updateClicked) { AutoUpdate.ignoreUpdate() } diff --git a/src/main/kotlin/gg/essential/mixins/ext/client/multiplayer/ServerDataExt.kt b/src/main/kotlin/gg/essential/mixins/ext/client/multiplayer/ServerDataExt.kt index 3d58b27..e025da2 100644 --- a/src/main/kotlin/gg/essential/mixins/ext/client/multiplayer/ServerDataExt.kt +++ b/src/main/kotlin/gg/essential/mixins/ext/client/multiplayer/ServerDataExt.kt @@ -29,6 +29,9 @@ interface ServerDataExt { /** If the ping was proxied, this indicates the region from which the proxied ping latency was measured. */ var `essential$pingRegion`: String? + /** A value that will be stored into the ServerData's ping field after ServerPing stores its calculated ping. */ + var `essential$pingOverride`: Long? + /** Whether to skip the mod compatibility check when connecting. */ var `essential$skipModCompatCheck`: Boolean @@ -46,6 +49,10 @@ var ServerDataExt.pingRegion get() = `essential$pingRegion` set(value) { `essential$pingRegion` = value } +var ServerDataExt.pingOverride + get() = `essential$pingOverride` + set(value) { `essential$pingOverride` = value } + var ServerDataExt.shareWithFriends get() = `essential$shareWithFriends` set(value) { `essential$shareWithFriends` = value } diff --git a/src/main/kotlin/gg/essential/mixins/ext/server/MinecraftServerExt.kt b/src/main/kotlin/gg/essential/mixins/ext/server/MinecraftServerExt.kt new file mode 100644 index 0000000..93527d7 --- /dev/null +++ b/src/main/kotlin/gg/essential/mixins/ext/server/MinecraftServerExt.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.mixins.ext.server + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import net.minecraft.server.MinecraftServer + +interface MinecraftServerExt { + val `essential$dispatcher`: CoroutineDispatcher + val `essential$coroutineScope`: CoroutineScope +} + +val MinecraftServer.ext get() = this as MinecraftServerExt +val MinecraftServer.dispatcher get() = ext.`essential$dispatcher` +val MinecraftServer.coroutineScope get() = ext.`essential$coroutineScope` diff --git a/src/main/kotlin/gg/essential/mixins/ext/server/integrated/IntegratedServerExt.kt b/src/main/kotlin/gg/essential/mixins/ext/server/integrated/IntegratedServerExt.kt new file mode 100644 index 0000000..4e02214 --- /dev/null +++ b/src/main/kotlin/gg/essential/mixins/ext/server/integrated/IntegratedServerExt.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.mixins.ext.server.integrated + +import gg.essential.sps.McIntegratedServerManager +import net.minecraft.server.integrated.IntegratedServer + +interface IntegratedServerExt { + val `essential$manager`: McIntegratedServerManager +} + +val IntegratedServer.ext get() = this as IntegratedServerExt +val IntegratedServer.manager get() = ext.`essential$manager` diff --git a/src/main/kotlin/gg/essential/model/backend/minecraft/MinecraftRenderBackend.kt b/src/main/kotlin/gg/essential/model/backend/minecraft/MinecraftRenderBackend.kt index b8432a8..eeb3c01 100644 --- a/src/main/kotlin/gg/essential/model/backend/minecraft/MinecraftRenderBackend.kt +++ b/src/main/kotlin/gg/essential/model/backend/minecraft/MinecraftRenderBackend.kt @@ -37,6 +37,10 @@ import java.io.ByteArrayInputStream import gg.essential.model.util.UMatrixStack as CMatrixStack import gg.essential.model.util.UVertexConsumer as CVertexConsumer +//#if MC>=12102 +//$$ import net.minecraft.client.render.VertexConsumer +//#endif + //#if MC>=11700 //$$ import org.lwjgl.opengl.GL30.glBindFramebuffer //$$ import org.lwjgl.opengl.GL30.glDeleteFramebuffers @@ -120,6 +124,39 @@ object MinecraftRenderBackend : RenderBackend { if (prevScissor) GL11.glEnable(GL11.GL_SCISSOR_TEST) } + //#if MC>=12102 + //$$ // MC no longer provides the CULL variant of ENTITY_TRANSLUCENT which we use to render our cosmetics, so we'll + //$$ // have to build it ourselves. + //$$ private val entityTranslucentCullLayers = mutableMapOf() + //$$ fun getEntityTranslucentCullLayer(texture: Identifier) = entityTranslucentCullLayers.getOrPut(texture) { + //$$ val inner = RenderLayer.getEntityTranslucent(texture) + //$$ // Note: If this is turned into an anonymous class, Kotlin will generate the bridge for the protected + //$$ // field in the wrong class (MinecraftRenderBackend), which will then throw an IllegalAccessError. + //$$ class EntityTranslucentCullLayer : RenderLayer( + //$$ "entity_translucent_cull", + //$$ inner.vertexFormat, + //$$ inner.drawMode, + //$$ inner.expectedBufferSize, + //$$ inner.hasCrumbling(), + //$$ inner.isTranslucent, + //$$ { + //$$ inner.startDrawing() + //$$ DISABLE_CULLING.endDrawing() + //$$ ENABLE_CULLING.startDrawing() + //$$ }, + //$$ { + //$$ ENABLE_CULLING.endDrawing() + //$$ DISABLE_CULLING.startDrawing() + //$$ inner.endDrawing() + //$$ }, + //$$ ) + //$$ EntityTranslucentCullLayer() + //$$ } + //#elseif MC>=11400 + //$$ fun getEntityTranslucentCullLayer(texture: ResourceLocation) = RenderType.getEntityTranslucentCull(texture) + //#endif + + //#if MC>=11400 //$$ // Neither of the render layers which vanilla provides across the versions quite does what we need, so we'll need //$$ // to construct one of our own (based on one of the existing ones, so it works with shaders). @@ -190,6 +227,16 @@ object MinecraftRenderBackend : RenderBackend { //$$ ) //$$ EmissiveArmorLayer() //$$ } + //#if MC>=12102 + //$$ object NullMcVertexConsumer : VertexConsumer { + //$$ override fun vertex(x: Float, y: Float, z: Float): VertexConsumer = this + //$$ override fun color(red: Int, green: Int, blue: Int, alpha: Int): VertexConsumer = this + //$$ override fun texture(u: Float, v: Float): VertexConsumer = this + //$$ override fun overlay(u: Int, v: Int): VertexConsumer = this + //$$ override fun light(u: Int, v: Int): VertexConsumer = this + //$$ override fun normal(x: Float, y: Float, z: Float): VertexConsumer = this + //$$ } + //#endif //#else private val MC_AMBIENT_LIGHT = BufferUtils.createFloatBuffer(4).apply { put(0.4f).put(0.4f).put(0.4f).put(1f) // see RenderHelper class @@ -327,7 +374,7 @@ object MinecraftRenderBackend : RenderBackend { //#if MC>=11600 //$$ val buffer = provider.getBuffer( //$$ if (emissive) getEmissiveLayer(texture.identifier) - //$$ else RenderType.getEntityTranslucentCull(texture.identifier) + //$$ else getEntityTranslucentCullLayer(texture.identifier) //$$ ) //$$ block(VertexConsumerAdapter(UVertexConsumer.of(buffer))) //#else diff --git a/src/main/kotlin/gg/essential/model/backend/minecraft/playerPose.kt b/src/main/kotlin/gg/essential/model/backend/minecraft/playerPose.kt index f22cc0b..dbb7114 100644 --- a/src/main/kotlin/gg/essential/model/backend/minecraft/playerPose.kt +++ b/src/main/kotlin/gg/essential/model/backend/minecraft/playerPose.kt @@ -29,6 +29,15 @@ import net.minecraft.client.model.ModelBiped import net.minecraft.client.model.ModelRenderer import net.minecraft.client.renderer.entity.RenderPlayer +//#if MC>=12102 +//$$ import dev.folomeev.kotgl.matrix.vectors.mutables.minus +//$$ import dev.folomeev.kotgl.matrix.vectors.mutables.plus +//$$ import dev.folomeev.kotgl.matrix.vectors.mutables.plusSelf +//$$ import gg.essential.model.util.rotateBy +//$$ import gg.essential.model.util.toMat3 +//$$ import gg.essential.model.util.toMat4 +//#endif + fun RenderPlayer.toPose(): PlayerPose { val basePose = mainModel.toPose() val features = (this as PlayerEntityRendererExt).`essential$getFeatures`() @@ -71,7 +80,9 @@ fun ModelBiped.toPose(): PlayerPose = PlayerPose( rightWing = PlayerPose.Part(), leftWing = PlayerPose.Part(), cape = PlayerPose.Part(), - //#if MC>=11600 && FABRIC + //#if MC>=12102 + //$$ false, + //#elseif MC>=11600 && FABRIC //$$ child, //#elseif MC>=11700 //$$ young, @@ -100,7 +111,9 @@ fun PlayerPose.applyTo(model: ModelBiped) { // FIXME preprocessor bug: for some reason it doesn't remap these //#if MC>=11600 && FABRIC || MC>=11700 //$$ head.applyTo(model.head) - //#if MC>=11700 + //#if MC>=12000 + //$$ // Hat is now a proper child of head + //#elseif MC>=11700 //$$ head.applyTo(model.hat) //#else //$$ head.applyTo(model.helmet) @@ -123,6 +136,7 @@ fun PlayerPose.applyTo(model: ModelBiped) { rightLeg.applyTo(model.bipedRightLeg) leftLeg.applyTo(model.bipedLeftLeg) //#endif + //#if MC<12102 if (model is ModelPlayerAccessor) { head.applyTo(model.ears) } @@ -133,6 +147,7 @@ fun PlayerPose.applyTo(model: ModelBiped) { //#else model.isChild = child //#endif + //#endif } fun PlayerPose.Part.applyTo(model: ModelRenderer) { @@ -145,6 +160,43 @@ fun PlayerPose.Part.applyTo(model: ModelRenderer) { (model as ExtraTransformHolder).extra = extra } +//#if MC>=12102 +//$$ fun PlayerPose.withCapePose( +//$$ extraOffset: Vec3, +//$$ bodyModel: ModelPart, +//$$ capeModel: ModelPart, +//$$ ): PlayerPose { +//$$ // Chain body pose and cape pose (which is a child of body) into a single absolute pose +//$$ val bodyPose = bodyModel.toPose() +//$$ val bodyPos = bodyPose.pivot.plus(extraOffset) +//$$ val bodyRot = bodyPose.rotation +//$$ val capePose = capeModel.toPose() +//$$ val pos = capePose.pivot.rotateBy(bodyRot).plusSelf(bodyPos) +//$$ val rotation = bodyRot.times(capePose.rotation) +//$$ val rot = rotation.toMat3().toMat4().getRotationEulerZYX() +//$$ val absPose = PlayerPose.Part(pos.x, pos.y, pos.z, rot.x, rot.y, rot.z) +//$$ return copy(cape = absPose) +//$$ } +//$$ +//$$ fun PlayerPose.applyCapePose( +//$$ extraOffset: Vec3, +//$$ bodyModel: ModelPart, +//$$ capeModel: ModelPart, +//$$ ) { +//$$ // Convert the absolute cape pose into one relative to the body pose (because capeModel is a child of bodyModel) +//$$ // i.e. inverse of withCapePose +//$$ val bodyPose = bodyModel.toPose() +//$$ val bodyPos = bodyPose.pivot.plus(extraOffset) +//$$ val bodyRot = bodyPose.rotation +//$$ val absPose = this.cape +//$$ val pos = absPose.pivot.minus(bodyPos).rotateBy(bodyRot.conjugate()) +//$$ val rotation = bodyRot.invert().times(absPose.rotation) +//$$ val rot = rotation.toMat3().toMat4().getRotationEulerZYX() +//$$ val relPose = PlayerPose.Part(pos.x, pos.y, pos.z, rot.x, rot.y, rot.z) +//$$ // and finally apply it to the ModelPart +//$$ relPose.applyTo(capeModel) +//$$ } +//#else fun PlayerPose.withCapePose( capeModel: ModelRenderer, vanillaMatrix: Mat4, @@ -171,6 +223,7 @@ fun PlayerPose.withCapePose( ) return copy(cape = combinedPose) } +//#endif fun getElytraPoseOffset(player: AbstractClientPlayer): Vec3 { val offset = mutableVec3() diff --git a/src/main/kotlin/gg/essential/network/connectionmanager/coins/CoinsManager.kt b/src/main/kotlin/gg/essential/network/connectionmanager/coins/CoinsManager.kt index 0b378a4..f3b0111 100644 --- a/src/main/kotlin/gg/essential/network/connectionmanager/coins/CoinsManager.kt +++ b/src/main/kotlin/gg/essential/network/connectionmanager/coins/CoinsManager.kt @@ -30,14 +30,11 @@ import gg.essential.connectionmanager.common.packet.response.ResponseActionPacke import gg.essential.gui.elementa.state.v2.* import gg.essential.gui.elementa.state.v2.combinators.* import gg.essential.gui.elementa.state.v2.stateBy -import gg.essential.gui.image.EssentialAssetImageFactory import gg.essential.gui.notification.Notifications import gg.essential.gui.notification.sendCheckoutFailedNotification import gg.essential.gui.wardrobe.modals.CoinsReceivedModal -import gg.essential.mod.EssentialAsset import gg.essential.network.connectionmanager.ConnectionManager import gg.essential.network.connectionmanager.NetworkedManager -import gg.essential.network.connectionmanager.cosmetics.* import gg.essential.network.connectionmanager.handler.checkout.ServerCheckoutPartnerCodeDataPacketHandler import gg.essential.network.connectionmanager.handler.coins.ServerCoinsBalancePacketHandler import gg.essential.network.cosmetics.toMod @@ -47,9 +44,6 @@ import java.text.DecimalFormat import java.text.DecimalFormatSymbols import java.util.* import java.util.concurrent.TimeUnit -import kotlin.math.max -import kotlin.math.pow -import kotlin.math.round class CoinsManager(val connectionManager: ConnectionManager) : NetworkedManager { @@ -272,37 +266,3 @@ class CoinsManager(val connectionManager: ConnectionManager) : NetworkedManager } } - -data class CoinBundle( - val id: String, - val numberOfCoins: Int, - val currency: Currency, - val price: Double, - val extraPercent: Int, - val iconAsset: EssentialAsset, - val isHighlighted: Boolean, - val isExchangeBundle: Boolean, - val isSpecificAmount: Boolean = false, -) { - - val iconFactory = EssentialAssetImageFactory(iconAsset) - - init { - iconFactory.primeCache(AssetLoader.Priority.Low) - } - - val formattedPrice: String = currency.format(price) - - fun getBundleForNumberOfCoins(coins: Int): CoinBundle { - val precision = 10.0.pow(currency.defaultFractionDigits) - val minimumBundleSize = Essential.getInstance().connectionManager.cosmeticsManager.wardrobeSettings.youNeedMinimumAmount.get() - val coinsAmount = max(minimumBundleSize, coins) - return copy( - numberOfCoins = coinsAmount, - price = round((price / numberOfCoins) * coinsAmount * precision) / precision, - isSpecificAmount = true - ) - } - -} - diff --git a/src/main/kotlin/gg/essential/network/connectionmanager/cosmetics/EmoteWheelManager.kt b/src/main/kotlin/gg/essential/network/connectionmanager/cosmetics/EmoteWheelManager.kt new file mode 100644 index 0000000..e0a79b2 --- /dev/null +++ b/src/main/kotlin/gg/essential/network/connectionmanager/cosmetics/EmoteWheelManager.kt @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.network.connectionmanager.cosmetics + +import gg.essential.Essential +import gg.essential.connectionmanager.common.packet.cosmetic.emote.ClientCosmeticEmoteWheelSelectPacket +import gg.essential.connectionmanager.common.packet.cosmetic.emote.ClientCosmeticEmoteWheelUpdatePacket +import gg.essential.connectionmanager.common.packet.cosmetic.emote.ServerCosmeticEmoteWheelPopulatePacket +import gg.essential.cosmetics.CosmeticId +import gg.essential.cosmetics.model.EmoteWheel +import gg.essential.gui.elementa.state.v2.MutableState +import gg.essential.gui.elementa.state.v2.clear +import gg.essential.gui.elementa.state.v2.memo +import gg.essential.gui.elementa.state.v2.mutableListStateOf +import gg.essential.gui.elementa.state.v2.mutableStateOf +import gg.essential.gui.elementa.state.v2.setAll +import gg.essential.mod.cosmetics.EmoteWheelPage +import gg.essential.network.connectionmanager.ConnectionManager +import gg.essential.network.connectionmanager.NetworkedManager +import gg.essential.network.cosmetics.toMod +import gg.essential.network.registerPacketHandler +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import java.util.* +import kotlin.time.Duration.Companion.seconds + +class EmoteWheelManager( + val connectionManager: ConnectionManager, + val cosmeticsManager: CosmeticsManager +) : NetworkedManager { + + private val emoteWheels = mutableListStateOf() + val orderedEmoteWheels = memo { + emoteWheels().sortedBy { it.createdAt.toEpochMilli() } + } + private val mutableSelectedEmoteWheelId: MutableState = mutableStateOf(null) + val selectedEmoteWheel = memo { orderedEmoteWheels().firstOrNull { it.id == mutableSelectedEmoteWheelId() } } + val selectedEmoteWheelIndex = memo { orderedEmoteWheels().indexOfFirst { it.id == mutableSelectedEmoteWheelId() } } + val selectedEmoteWheelSlots = memo { + selectedEmoteWheel()?.slots ?: List(EmoteWheelPage.SLOTS) { null } + } + + private var flushJob: Job? = null + private var sentEmoteWheelId: String? = null + + init { + connectionManager.registerPacketHandler { packet -> + populateEmoteWheels(packet.emoteWheels()) + } + } + + override fun resetState() { + emoteWheels.clear() + sentEmoteWheelId = null + } + + fun selectEmoteWheel(id: String) { + val emoteWheel = orderedEmoteWheels.getUntracked().find { it.id == id } ?: return + mutableSelectedEmoteWheelId.set(emoteWheel.id) + flushSelectedEmoteWheel(true) + } + + /** + * Selects the emote wheel at a given offset from the currently selected emote wheel, wrapping around if necessary. + * + * @return The index of the newly selected emote wheel or null if no emote wheels are present + */ + fun shiftSelectedEmoteWheel(offset: Int): Int? { + val numWheels = orderedEmoteWheels.getUntracked().size + if (numWheels == 0) { + return null + } + val curIndex = selectedEmoteWheelIndex.getUntracked() + val newIndex = (curIndex + offset + numWheels) % numWheels + selectEmoteWheel(orderedEmoteWheels.getUntracked()[newIndex].id) + return newIndex + } + + private fun populateEmoteWheels(emoteWheels: List) { + this.emoteWheels.setAll(emoteWheels.map { it.toMod() }) + + sentEmoteWheelId = emoteWheels.find { it.selected() }?.id() + mutableSelectedEmoteWheelId.set(sentEmoteWheelId) + + if (mutableSelectedEmoteWheelId.getUntracked() == null) { + Essential.logger.error("No emote wheel was selected, selecting the first one.") + emoteWheels.firstOrNull()?.id()?.let { selectEmoteWheel(it) } + } + } + + fun flushSelectedEmoteWheel(debounce: Boolean) { + flushJob?.cancel() + if (debounce) { + flushJob = connectionManager.connectionScope.launch { + delay(3.seconds) + flushSelectedEmoteWheel(false) + } + return + } + + val selectedEmoteWheelId = mutableSelectedEmoteWheelId.getUntracked() ?: return + if (selectedEmoteWheelId == sentEmoteWheelId) { + return + } + sentEmoteWheelId = selectedEmoteWheelId + connectionManager.call(ClientCosmeticEmoteWheelSelectPacket(selectedEmoteWheelId)).fireAndForget() + } + + + /** + * Sets the saved emotes for the emote wheel + * + * @param emotes The (CosmeticId) list of emotes to save + */ + fun setEmotes(emotes: List) { + val selectedEmoteWheel = selectedEmoteWheel.getUntracked() ?: return + val slots = selectedEmoteWheelSlots.getUntracked().toMutableList() + val unlockedCosmetics = cosmeticsManager.unlockedCosmetics.getUntracked() + for ((i, value) in emotes.withIndex()) { + if (value != null && !unlockedCosmetics.contains(value)) { + continue + } + + if (value != slots.set(i, value)) { + connectionManager.call(ClientCosmeticEmoteWheelUpdatePacket(selectedEmoteWheel.id, i, value)).fireAndForget() + } + } + editEmoteWheel(selectedEmoteWheel.copy(slots = slots.toList())) + } + + /** + * Changes one of the emotes saved for the emote wheel. + */ + fun setEmote(slotIndex: Int, emoteId: String?) { + setEmotes(ArrayList(selectedEmoteWheelSlots.getUntracked()).apply { this[slotIndex] = emoteId }) + } + + private fun editEmoteWheel(new: EmoteWheelPage) { + emoteWheels.set { list -> + list.set(list.indexOfFirst { it.id == new.id }, new) + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/gg/essential/network/connectionmanager/cosmetics/EquippedCosmeticsManager.kt b/src/main/kotlin/gg/essential/network/connectionmanager/cosmetics/EquippedCosmeticsManager.kt index 5f2d99a..7d81d5b 100644 --- a/src/main/kotlin/gg/essential/network/connectionmanager/cosmetics/EquippedCosmeticsManager.kt +++ b/src/main/kotlin/gg/essential/network/connectionmanager/cosmetics/EquippedCosmeticsManager.kt @@ -211,7 +211,12 @@ class EquippedCosmeticsManager( //#else if (gameSettings.modelParts.contains(part) != enabled) { //#endif + //#if MC>=12102 + //$$ gameSettings.setPlayerModelPart(part, enabled) + //$$ gameSettings.sendClientSettings() + //#else gameSettings.setModelPartEnabled(part, enabled) + //#endif } } } diff --git a/src/main/kotlin/gg/essential/network/connectionmanager/cosmetics/OutfitManager.kt b/src/main/kotlin/gg/essential/network/connectionmanager/cosmetics/OutfitManager.kt index 7807b8c..7cf843e 100644 --- a/src/main/kotlin/gg/essential/network/connectionmanager/cosmetics/OutfitManager.kt +++ b/src/main/kotlin/gg/essential/network/connectionmanager/cosmetics/OutfitManager.kt @@ -34,7 +34,6 @@ import gg.essential.gui.elementa.state.v2.onChange import gg.essential.gui.elementa.state.v2.remove import gg.essential.gui.elementa.state.v2.stateBy import gg.essential.gui.elementa.state.v2.toListState -import gg.essential.gui.emotes.EmoteWheel import gg.essential.gui.notification.Notifications import gg.essential.handlers.GameProfileManager import gg.essential.mod.Skin @@ -146,21 +145,13 @@ class OutfitManager( * Sets the given outfit as the active one. * This runs [flushSelectedOutfit] afterwards to save selected outfit to infra with debounce. * To save immediately use [flushSelectedOutfit] with debounce false after calling. - * - * @return The previously active outfit, if any */ - private fun setSelectedOutfit(outfit: CosmeticOutfit?): CosmeticOutfit? { - val oldOutfit: CosmeticOutfit? = getSelectedOutfit() + private fun setSelectedOutfit(outfit: CosmeticOutfit?) { this.mutableSelectedOutfitId.set(outfit?.id) if (outfit == null) { - return null - } - - if (oldOutfit != null) { - EmoteWheel.unequipCurrentEmote(oldOutfit) + return } flushSelectedOutfit(true) - return oldOutfit } fun populateOutfits(outfits: List) { @@ -287,6 +278,21 @@ class OutfitManager( val equippedCosmetics = outfit.equippedCosmetics.toMutableMap() if (cosmeticId != null) { + // All slots that the equipped cosmetic is mutually exclusive with + for (mutuallyExclusiveSlot in cosmeticsManager.getCosmetic(cosmeticId)?.mutuallyExclusiveWith ?: emptySet()) { + if (equippedCosmetics.containsKey(mutuallyExclusiveSlot)) { + equippedCosmetics.remove(mutuallyExclusiveSlot) + packetQueue.enqueue(ClientCosmeticOutfitEquippedCosmeticsUpdatePacket(outfit.id, mutuallyExclusiveSlot.toInfra(), null)) + } + } + // All other slots with an equipped cosmetic that is mutually exclusive with the newly equipped slot + for ((equippedSlot, equippedCosmeticId) in equippedCosmetics.entries) { + if (cosmeticsManager.getCosmetic(equippedCosmeticId)?.mutuallyExclusiveWith?.contains(slot) == true) { + equippedCosmetics.remove(equippedSlot) + packetQueue.enqueue(ClientCosmeticOutfitEquippedCosmeticsUpdatePacket(outfit.id, equippedSlot.toInfra(), null)) + } + } + equippedCosmetics[slot] = cosmeticId } else { equippedCosmetics.remove(slot) diff --git a/src/main/kotlin/gg/essential/network/connectionmanager/handler/upnp/ServerUPnPSessionInviteAddPacketHandler.kt b/src/main/kotlin/gg/essential/network/connectionmanager/handler/upnp/ServerUPnPSessionInviteAddPacketHandler.kt index f7caf34..ff65037 100644 --- a/src/main/kotlin/gg/essential/network/connectionmanager/handler/upnp/ServerUPnPSessionInviteAddPacketHandler.kt +++ b/src/main/kotlin/gg/essential/network/connectionmanager/handler/upnp/ServerUPnPSessionInviteAddPacketHandler.kt @@ -66,6 +66,7 @@ class ServerUPnPSessionInviteAddPacketHandler : PacketHandler, isInvited: (UUID) -> Boolean, ) : IceManagerImpl(cmConnection, baseDir.resolve("ice-logs"), isInvited), IIceManager { + private val refHolder = ReferenceHolderImpl() + override var integratedServerVoicePort: Int = 0 + init { + effect(refHolder) { + integratedServerVoicePort = integratedServerManager()?.thirdPartyVoicePort() ?: return@effect + } + } + override fun setVoicePort(port: Int) { this.integratedServerVoicePort = port } diff --git a/src/main/kotlin/gg/essential/network/connectionmanager/notices/FriendRequestToastNoticeListener.kt b/src/main/kotlin/gg/essential/network/connectionmanager/notices/FriendRequestToastNoticeListener.kt index aadb5b4..3b042fb 100644 --- a/src/main/kotlin/gg/essential/network/connectionmanager/notices/FriendRequestToastNoticeListener.kt +++ b/src/main/kotlin/gg/essential/network/connectionmanager/notices/FriendRequestToastNoticeListener.kt @@ -17,7 +17,6 @@ import gg.essential.gui.notification.Notifications import gg.essential.gui.notification.content.ConfirmDenyNotificationActionComponent import gg.essential.gui.notification.markdownBody import gg.essential.network.connectionmanager.ConnectionManager -import gg.essential.network.connectionmanager.notices.NoticesManager.NoticeListener import gg.essential.notices.NoticeType import gg.essential.notices.model.Notice import gg.essential.util.UUIDUtil diff --git a/src/main/kotlin/gg/essential/network/connectionmanager/notices/GiftedCosmeticNoticeListener.kt b/src/main/kotlin/gg/essential/network/connectionmanager/notices/GiftedCosmeticNoticeListener.kt index 1fdf7c2..7b30f1c 100644 --- a/src/main/kotlin/gg/essential/network/connectionmanager/notices/GiftedCosmeticNoticeListener.kt +++ b/src/main/kotlin/gg/essential/network/connectionmanager/notices/GiftedCosmeticNoticeListener.kt @@ -12,7 +12,6 @@ package gg.essential.network.connectionmanager.notices import gg.essential.gui.wardrobe.components.showGiftReceivedToast -import gg.essential.network.connectionmanager.notices.NoticesManager.NoticeListener import gg.essential.notices.NoticeType import gg.essential.notices.model.Notice import gg.essential.util.UUIDUtil diff --git a/src/main/kotlin/gg/essential/network/connectionmanager/notices/NoticeBannerManager.kt b/src/main/kotlin/gg/essential/network/connectionmanager/notices/NoticeBannerManager.kt index bd93207..07a3cb7 100644 --- a/src/main/kotlin/gg/essential/network/connectionmanager/notices/NoticeBannerManager.kt +++ b/src/main/kotlin/gg/essential/network/connectionmanager/notices/NoticeBannerManager.kt @@ -15,7 +15,6 @@ import gg.essential.elementa.utils.withAlpha import gg.essential.gui.EssentialPalette import gg.essential.gui.elementa.state.v2.* import gg.essential.gui.wardrobe.WardrobeCategory -import gg.essential.network.connectionmanager.notices.NoticesManager.NoticeListener import gg.essential.notices.NoticeType import gg.essential.notices.model.Notice import java.awt.Color diff --git a/src/main/kotlin/gg/essential/network/connectionmanager/notices/PersistentToastNoticeListener.kt b/src/main/kotlin/gg/essential/network/connectionmanager/notices/PersistentToastNoticeListener.kt index 2d3abd9..bcd41f8 100644 --- a/src/main/kotlin/gg/essential/network/connectionmanager/notices/PersistentToastNoticeListener.kt +++ b/src/main/kotlin/gg/essential/network/connectionmanager/notices/PersistentToastNoticeListener.kt @@ -16,7 +16,6 @@ import gg.essential.event.gui.GuiOpenEvent import gg.essential.gui.notification.Notifications import gg.essential.gui.wardrobe.Wardrobe import gg.essential.gui.wardrobe.WardrobeCategory -import gg.essential.network.connectionmanager.notices.NoticesManager.NoticeListener import gg.essential.network.connectionmanager.telemetry.TelemetryManager import gg.essential.notices.NoticeType import gg.essential.notices.model.Notice diff --git a/src/main/kotlin/gg/essential/network/connectionmanager/notices/SocialMenuNewFriendRequestNoticeManager.kt b/src/main/kotlin/gg/essential/network/connectionmanager/notices/SocialMenuNewFriendRequestNoticeManager.kt index 94dc0ea..6bb1dcf 100644 --- a/src/main/kotlin/gg/essential/network/connectionmanager/notices/SocialMenuNewFriendRequestNoticeManager.kt +++ b/src/main/kotlin/gg/essential/network/connectionmanager/notices/SocialMenuNewFriendRequestNoticeManager.kt @@ -13,7 +13,6 @@ package gg.essential.network.connectionmanager.notices import gg.essential.elementa.state.BasicState import gg.essential.gui.common.WeakState -import gg.essential.network.connectionmanager.notices.NoticesManager.NoticeListener import gg.essential.notices.NoticeType import gg.essential.notices.model.Notice import java.util.UUID diff --git a/src/main/kotlin/gg/essential/network/connectionmanager/relationship/RelationshipErrorResponse.kt b/src/main/kotlin/gg/essential/network/connectionmanager/relationship/relationshipErrorResponse.kt similarity index 72% rename from src/main/kotlin/gg/essential/network/connectionmanager/relationship/RelationshipErrorResponse.kt rename to src/main/kotlin/gg/essential/network/connectionmanager/relationship/relationshipErrorResponse.kt index e744779..12e6995 100644 --- a/src/main/kotlin/gg/essential/network/connectionmanager/relationship/RelationshipErrorResponse.kt +++ b/src/main/kotlin/gg/essential/network/connectionmanager/relationship/relationshipErrorResponse.kt @@ -16,93 +16,66 @@ import gg.essential.gui.notification.Notifications import gg.essential.gui.notification.error import gg.essential.gui.notification.markdownBody import gg.essential.gui.notification.warning +import gg.essential.network.connectionmanager.relationship.RelationshipErrorResponse.* import gg.essential.util.colored import net.minecraft.client.resources.I18n -import java.util.* +import java.util.UUID -enum class RelationshipErrorResponse( - val showToast: (UUID, String) -> Unit -) { +val RelationshipErrorResponse.message: String + get() = I18n.format("connectionmanager.friends." + name) - EXISTING_REQUEST_IS_PENDING({ _, name -> +fun RelationshipErrorResponse.showToast(uuid: UUID, name: String) = when (this) { + EXISTING_REQUEST_IS_PENDING -> Notifications.error("Friend request failed", "") { markdownBody("Friend request already sent to ${name.colored(EssentialPalette.TEXT_HIGHLIGHT)}.") } - }), - - SENDER_TYPE_VERIFIED_SLOT_LIMIT({ _, _ -> + SENDER_TYPE_VERIFIED_SLOT_LIMIT -> Notifications.error( "Friend request failed", "Your friends list is full. Remove friends before adding new ones." ) - }), - TARGET_TYPE_VERIFIED_SLOT_LIMIT({ _, name -> + TARGET_TYPE_VERIFIED_SLOT_LIMIT -> Notifications.error("Friend request failed", "") { markdownBody("${name.colored(EssentialPalette.TEXT_HIGHLIGHT)}'s friends list is full.") } - }), - NO_PENDING_REQUEST_TO_VERIFY({ _, _ -> + NO_PENDING_REQUEST_TO_VERIFY -> Notifications.error("Error", "No pending friend request to accept.") - }), - VERIFIED_RELATIONSHIP_ALREADY_EXISTS({ _, name -> + VERIFIED_RELATIONSHIP_ALREADY_EXISTS -> Notifications.error("Friend request failed", "") { markdownBody("You are already friends with ${name.colored(EssentialPalette.TEXT_HIGHLIGHT)}.") } - }), - NO_BLOCKED_RELATIONSHIP_TO_CONVERT({ _, name -> + NO_BLOCKED_RELATIONSHIP_TO_CONVERT -> Notifications.error("Error", "") { markdownBody("Failed to block ${name.colored(EssentialPalette.TEXT_HIGHLIGHT)}.") } - }), - SENDER_BLOCKED_TARGET({ _, name -> + SENDER_BLOCKED_TARGET -> Notifications.error("Friend request failed", "") { markdownBody("You have blocked ${name.colored(EssentialPalette.TEXT_HIGHLIGHT)}. " + "Unblock them before sending them a friend request.") } - }), - TARGET_BLOCKED_SENDER({ _, name -> + TARGET_BLOCKED_SENDER -> Notifications.warning("Friend request declined", "") { markdownBody("${name.colored(EssentialPalette.TEXT_HIGHLIGHT)} has blocked you.") } - }), - SENDER_TYPE_OUTGOING_SLOT_LIMIT({ _, _ -> + SENDER_TYPE_OUTGOING_SLOT_LIMIT -> Notifications.error( "Friend request failed", "You have too many pending friend requests. Remove some to make space for new ones." ) - }), - TARGET_TYPE_INCOMING_SLOT_LIMIT({ _, name -> + TARGET_TYPE_INCOMING_SLOT_LIMIT -> Notifications.error("Friend request failed", "") { markdownBody("${name.colored(EssentialPalette.TEXT_HIGHLIGHT)} has too many pending friend requests.") } - }), - TARGET_PRIVACY_SETTING_FRIEND_OF_FRIENDS({ _, name -> + TARGET_PRIVACY_SETTING_FRIEND_OF_FRIENDS -> Notifications.warning("Friend request declined", "") { markdownBody("${name.colored(EssentialPalette.TEXT_HIGHLIGHT)} is only accepting friend requests from friends of friends.") } - }), - TARGET_PRIVACY_SETTING_NO_ONE({ _, name -> + TARGET_PRIVACY_SETTING_NO_ONE -> Notifications.warning("Friend request declined", "") { markdownBody("${name.colored(EssentialPalette.TEXT_HIGHLIGHT)} is not accepting any friend requests.") } - }), - TARGET_NOT_EXIST({ _, name -> + TARGET_NOT_EXIST -> Notifications.error("Error", "") { markdownBody("${name.colored(EssentialPalette.TEXT_HIGHLIGHT)} does not exist.") } - }); - - val message: String - get() = I18n.format("connectionmanager.friends." + name) - - companion object { - @JvmStatic - fun getResponse(response: String): RelationshipErrorResponse? { - return try { - valueOf(response) - } catch (e: IllegalArgumentException) { - null - } - } - } } diff --git a/src/main/kotlin/gg/essential/network/connectionmanager/relationship/relationshipResponse.kt b/src/main/kotlin/gg/essential/network/connectionmanager/relationship/relationshipResponse.kt new file mode 100644 index 0000000..923a202 --- /dev/null +++ b/src/main/kotlin/gg/essential/network/connectionmanager/relationship/relationshipResponse.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.network.connectionmanager.relationship + +import gg.essential.util.UUIDUtil +import java.util.* + +val RelationshipResponse.message: String? + get() = rawMessage ?: relationshipErrorResponse?.message + +fun RelationshipResponse.displayToast(uuid: UUID) { + UUIDUtil.getName(uuid).thenAccept { username -> + this.relationshipErrorResponse?.showToast( + uuid, + username + ) + } +} diff --git a/src/main/kotlin/gg/essential/network/connectionmanager/skins/SkinsManager.kt b/src/main/kotlin/gg/essential/network/connectionmanager/skins/SkinsManager.kt index f5e6988..24c76b6 100644 --- a/src/main/kotlin/gg/essential/network/connectionmanager/skins/SkinsManager.kt +++ b/src/main/kotlin/gg/essential/network/connectionmanager/skins/SkinsManager.kt @@ -26,7 +26,6 @@ import gg.essential.gui.elementa.state.v2.* import gg.essential.gui.elementa.state.v2.combinators.* import gg.essential.gui.notification.Notifications import gg.essential.gui.notification.error -import gg.essential.gui.wardrobe.Item import gg.essential.mod.Model import gg.essential.mod.Skin import gg.essential.network.connectionmanager.ConnectionManager @@ -43,13 +42,13 @@ class SkinsManager(val connectionManager: ConnectionManager) : NetworkedManager private val packetQueue = SequentialPacketQueue.Builder(connectionManager).onTimeoutSkip().create() // Actual data - private val mutableSkins = mutableStateOf>(mapOf()) + private val mutableSkins = mutableStateOf>(mapOf()) // Derived data - val skins: State> = mutableSkins + val skins: State> = mutableSkins val skinsOrdered = mutableSkins.map { skins -> skins.values.sortedWith( - compareBy { it.favoritedSince?.toEpochMilli() } + compareBy { it.favoritedSince?.toEpochMilli() } .thenByDescending { (it.lastUsedAt ?: it.createdAt).toEpochMilli() } ) }.toListState() @@ -77,8 +76,8 @@ class SkinsManager(val connectionManager: ConnectionManager) : NetworkedManager } } - fun addSkin(name: String, skin: Skin, selectSkin: Boolean = true, favorite: Boolean = false): CompletableFuture { - val future = CompletableFuture() + fun addSkin(name: String, skin: Skin, selectSkin: Boolean = true, favorite: Boolean = false): CompletableFuture { + val future = CompletableFuture() connectionManager.send(ClientSkinCreatePacket(name, skin.model.toInfra(), skin.hash, favorite)) { maybeResponse -> val response = maybeResponse.orElse(null) if (response is ServerSkinPopulatePacket) { @@ -90,7 +89,7 @@ class SkinsManager(val connectionManager: ConnectionManager) : NetworkedManager // We don't add the skin here, since the packet handler adds it already val skinItem = skinResponse.first().toMod() if (selectSkin) { - selectSkin(skinItem) + selectSkin(skinItem.id) } future.complete(skinItem) } else { @@ -100,31 +99,32 @@ class SkinsManager(val connectionManager: ConnectionManager) : NetworkedManager return future } - fun openDeleteSkinModal(skin: Item.SkinItem) { - if (connectionManager.outfitManager.outfits.get().any { it.skinId == skin.id }) { + fun openDeleteSkinModal(skinId: SkinId) { + val skin = skins.getUntracked()[skinId] ?: return + if (connectionManager.outfitManager.outfits.get().any { it.skinId == skinId }) { Notifications.error("Can’t delete skin", "Skin is currently used on one or more outfits.") return } GuiUtil.pushModal { manager -> DangerConfirmationEssentialModal(manager, "Delete", false) .configure { contentText = "Are you sure you want to delete\n${skin.name}?" } - .onPrimaryAction { deleteSkin(skin) } + .onPrimaryAction { deleteSkin(skinId) } } } - private fun deleteSkin(skin: Item.SkinItem) { - connectionManager.send(ClientSkinDeletePacket(skin.id)) { maybeResponse -> + private fun deleteSkin(skinId: SkinId) { + connectionManager.send(ClientSkinDeletePacket(skinId)) { maybeResponse -> val response = maybeResponse.orElse(null) if (response is ResponseActionPacket && response.isSuccessful) { - mutableSkins.set { it - skin.id } + mutableSkins.set { it - skinId } } else { Notifications.push("Error deleting skin", "An unexpected error has occurred. Try again.") } } } - fun setFavoriteState(skin: Item.SkinItem, favorite: Boolean) { - packetQueue.enqueue(ClientSkinUpdateFavoriteStatePacket(skin.id, favorite)) { maybeResponse -> + fun setFavoriteState(skinId: SkinId, favorite: Boolean) { + packetQueue.enqueue(ClientSkinUpdateFavoriteStatePacket(skinId, favorite)) { maybeResponse -> val response = maybeResponse.orElse(null) // We don't replace the skin here, since the packet handler already replaces it if (response !is ServerSkinPopulatePacket) { @@ -133,8 +133,8 @@ class SkinsManager(val connectionManager: ConnectionManager) : NetworkedManager } } - fun renameSkin(skin: Item.SkinItem, name: String) { - packetQueue.enqueue(ClientSkinUpdateNamePacket(skin.id, name)) { maybeResponse -> + fun renameSkin(skinId: SkinId, name: String) { + packetQueue.enqueue(ClientSkinUpdateNamePacket(skinId, name)) { maybeResponse -> val response = maybeResponse.orElse(null) // We don't replace the skin here, since the packet handler already replaces it if (response !is ServerSkinPopulatePacket) { @@ -143,8 +143,8 @@ class SkinsManager(val connectionManager: ConnectionManager) : NetworkedManager } } - fun updateLastUsedAtState(skin: Item.SkinItem) { - packetQueue.enqueue(ClientSkinUpdateLastUsedStatePacket(skin.id)) { maybeResponse -> + fun updateLastUsedAtState(skinId: SkinId) { + packetQueue.enqueue(ClientSkinUpdateLastUsedStatePacket(skinId)) { maybeResponse -> val response = maybeResponse.orElse(null) // We don't replace the skin here, since the packet handler already replaces it if (response !is ServerSkinPopulatePacket) { @@ -153,8 +153,9 @@ class SkinsManager(val connectionManager: ConnectionManager) : NetworkedManager } } - fun setSkinModel(skin: Item.SkinItem, model: Model) { - packetQueue.enqueue(ClientSkinUpdateDataPacket(skin.id, model.toInfra(), skin.skin.hash)) { maybeResponse -> + fun setSkinModel(skinId: SkinId, model: Model) { + val skin = skins.getUntracked()[skinId] ?: return + packetQueue.enqueue(ClientSkinUpdateDataPacket(skinId, model.toInfra(), skin.skin.hash)) { maybeResponse -> val response = maybeResponse.orElse(null) // We don't replace the skin here, since the packet handler already replaces it if (response !is ServerSkinPopulatePacket) { @@ -163,11 +164,11 @@ class SkinsManager(val connectionManager: ConnectionManager) : NetworkedManager } } - fun selectSkin(skin: Item.SkinItem) { - connectionManager.outfitManager.updateOutfitSkin(skin.id, false) + fun selectSkin(skinId: SkinId) { + connectionManager.outfitManager.updateOutfitSkin(skinId, false) } - fun onSkinPopulate(skins: Map) { + fun onSkinPopulate(skins: Map) { mutableSkins.set { map -> map + skins } } diff --git a/src/main/kotlin/gg/essential/network/cosmetics/cape/MojangCapeApi.kt b/src/main/kotlin/gg/essential/network/cosmetics/cape/MojangCapeApi.kt index 51b34f1..0af7130 100644 --- a/src/main/kotlin/gg/essential/network/cosmetics/cape/MojangCapeApi.kt +++ b/src/main/kotlin/gg/essential/network/cosmetics/cape/MojangCapeApi.kt @@ -30,6 +30,10 @@ import java.io.IOException object MojangCapeApi { private val JSON = MediaType.parse("application/json") + private val URL_BASE = System.getProperty( + "essential.mojang_profile_url", + "https://api.minecraftservices.com/minecraft/profile", + ) fun fetchCurrentTextures(): Property = MojangSkinManager.getTextureProperty(UUIDUtil.getClientUUID()) ?: throw IOException("Failed to fetch current texture property") @@ -38,7 +42,7 @@ object MojangCapeApi { val accessToken = Minecraft.getMinecraft().session.token val request = Request.Builder().apply { - url("https://api.minecraftservices.com/minecraft/profile") + url(URL_BASE) header("Authorization", "Bearer $accessToken") }.build() @@ -53,7 +57,7 @@ object MojangCapeApi { val accessToken = Minecraft.getMinecraft().session.token val request = Request.Builder().apply { - url("https://api.minecraftservices.com/minecraft/profile/capes/active") + url("$URL_BASE/capes/active") header("Authorization", "Bearer $accessToken") if (id != null) { data class Payload(val capeId: String) diff --git a/src/main/kotlin/gg/essential/network/pingproxy/ProxyPingServer.kt b/src/main/kotlin/gg/essential/network/pingproxy/ProxyPingServer.kt index 4e1faa7..ce1fdf8 100644 --- a/src/main/kotlin/gg/essential/network/pingproxy/ProxyPingServer.kt +++ b/src/main/kotlin/gg/essential/network/pingproxy/ProxyPingServer.kt @@ -16,6 +16,7 @@ import gg.essential.Essential import gg.essential.connectionmanager.common.packet.pingproxy.ClientPingProxyPacket import gg.essential.connectionmanager.common.packet.pingproxy.ServerPingProxyResponsePacket import gg.essential.mixins.ext.client.multiplayer.ext +import gg.essential.mixins.ext.client.multiplayer.pingOverride import gg.essential.mixins.ext.client.multiplayer.pingRegion import gg.essential.network.connectionmanager.AsyncResponseHandler import gg.essential.network.connectionmanager.ice.netty.CloseAfterFirstMessage @@ -107,24 +108,28 @@ class ProxyPingPacketHandler(val serverData: ServerData): SimpleChannelInboundHa } } else { if (id == 0x00) { // Status Request - Essential.getInstance().connectionManager.send(pingData!!, AsyncResponseHandler { maybeResponse -> + val pingData = pingData!! + Essential.getInstance().connectionManager.send(pingData, AsyncResponseHandler { maybeResponse -> val response = maybeResponse.orElse(null) as? ServerPingProxyResponsePacket if (response != null) { sendPacket(0x00) { // Query Response writeString(response.rawJson) } - // We can safely set these here, since we don't respond to the Ping with a Pong, see below - serverData.pingToServer = response.latency + // If we set the ping directly, it will get override with the value calculated in ServerPinger + // This override value is copied to the ping field after the calculated value is set, see Mixin_OverridePing + serverData.ext.pingOverride = response.latency serverData.ext.pingRegion = response.region } else { // If we didn't get a response from CM, just force disconnect which shows "(no connection)" in the gui channel.close() - Essential.logger.info("Received no response from ping proxy for ${pingData?.hostname}:${pingData?.port} (${pingData?.protocolVersion})") + Essential.logger.info("Received no response from ping proxy for ${pingData.hostname}:${pingData.port} (${pingData.protocolVersion})") } }, TimeUnit.SECONDS, 7) } else if (id == 0x01) { // Ping - // Instead of responding to the ping with a pong, close the connection so that the client doesn't write the latency to the ServerData - channel.close() + val payload = buf.readLong() + sendPacket(0x01) { // Pong + writeLong(payload) + } } } } diff --git a/src/main/kotlin/gg/essential/sps/McIntegratedServerManager.kt b/src/main/kotlin/gg/essential/sps/McIntegratedServerManager.kt new file mode 100644 index 0000000..60d432c --- /dev/null +++ b/src/main/kotlin/gg/essential/sps/McIntegratedServerManager.kt @@ -0,0 +1,343 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.sps + +import com.mojang.authlib.GameProfile +import gg.essential.compat.PlasmoVoiceCompat +import gg.essential.gui.elementa.state.v2.ListState +import gg.essential.gui.elementa.state.v2.MutableListState +import gg.essential.gui.elementa.state.v2.MutableState +import gg.essential.gui.elementa.state.v2.ReferenceHolderImpl +import gg.essential.gui.elementa.state.v2.State +import gg.essential.gui.elementa.state.v2.effect +import gg.essential.gui.elementa.state.v2.filter +import gg.essential.gui.elementa.state.v2.memo +import gg.essential.gui.elementa.state.v2.mutableListStateOf +import gg.essential.gui.elementa.state.v2.mutableStateOf +import gg.essential.gui.elementa.state.v2.withSetter +import gg.essential.mixins.ext.server.coroutineScope +import gg.essential.mixins.transformers.server.integrated.LanConnectionsAccessor +import gg.essential.sps.IntegratedServerManager.Difficulty +import gg.essential.sps.IntegratedServerManager.GameMode +import gg.essential.sps.IntegratedServerManager.ServerResourcePack +import gg.essential.universal.wrappers.UPlayer +import gg.essential.universal.wrappers.message.UTextComponent +import gg.essential.util.Client +import gg.essential.util.ModLoaderUtil +import gg.essential.util.USession +import gg.essential.util.UuidNameLookup +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.future.asDeferred +import kotlinx.coroutines.launch +import kotlinx.coroutines.plus +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext +import net.minecraft.client.resources.I18n +import net.minecraft.server.integrated.IntegratedServer +import net.minecraft.server.management.UserListWhitelistEntry +import java.nio.file.Path +import net.minecraft.world.EnumDifficulty as McDifficulty +import net.minecraft.world.GameType as McGameMode +import java.util.* + +class McIntegratedServerManager(val server: IntegratedServer) : IntegratedServerManager { + private val refHolder = ReferenceHolderImpl() + + override val worldFolder: Path = + //#if MC>=11600 + //$$ server.func_240776_a_(net.minecraft.world.storage.FolderName.DOT) + //#else + server.dataDirectory.toPath().resolve("saves").resolve(server.folderName) + //#endif + + override val serverPort: MutableState = mutableStateOf(null) + override val thirdPartyVoicePort: MutableState = mutableStateOf(null) + override val connectedPlayers: MutableListState = mutableListStateOf() + + private val hostUuid = USession.activeNow().uuid + override val connectedGuests: ListState + get() = connectedPlayers.filter { it != hostUuid } + + override val coroutineScope: CoroutineScope + get() = server.coroutineScope + Dispatchers.Client + + private val mutableStatusResponseJson = mutableStateOf(null) + override val statusResponseJson: State = mutableStatusResponseJson + + private val openToLanSourceState = mutableStateOf?>(null) + private val whitelistSourceState = mutableStateOf>?>(null) + private val opsSourceState = mutableStateOf>?>(null) + private val resourcePackSourceState = mutableStateOf?>(null) + private val difficultySourceState = mutableStateOf?>(null) + private val defaultGameModeSourceState = mutableStateOf?>(null) + private val cheatsEnabledSourceState = mutableStateOf?>(null) + private var openToLanUpdateJob: Job? = null + private var whitelistUpdateJob: Job? = null + private var opsUpdateJob: Job? = null + + //#if MC>=11900 + //$$ // For Mixin_IntegratedServerResourcePack only + //$$ var appliedServerResourcePack: Optional? = null + //#endif + + //#if MC>=11600 + //$$ // For Mixin_ControlAreCommandsAllowed only + //$$ var appliedCheatsEnabled: Boolean? = null + //#endif + + init { + effect(refHolder) { + val openToLan = (openToLanSourceState() ?: return@effect)() + + // The vanilla IntegratedServer.openToLan method is quite thread unsafe, it does non-trivial + // accesses to both client and server state, and vanilla can also call it from either the client + // thread (via the Open To LAN button) or the server thread (via /publish command). + // We'll schedule our task on the server thread but also make it block the client thread while it's + // executing, so we won't have to worry about those thread unsafe calls. This does risk dead locking if at + // the same time a client thread tries to run something on the server in a blocking way, but that seems + // decently unlikely, and is better than some race-induced state corruption, so we'll take it. + val prevJob = openToLanUpdateJob + openToLanUpdateJob = server.coroutineScope.launch { + // Keep updates serialized so we don't end up applying the wrong thing last. + // We cannot just cancel the previous job because openToLan can only be called once, and if the previous + // job has already called it, we need to allow it to finish. + prevJob?.join() + + if (openToLan && !server.public) { + // IntegratedServer.openToLan assumes that the client is fully connected (mc.player is initialized). + // This may however not yet be the case here, e.g. it is possible to set this State immediately + // as soon as the server becomes available, while the client is still handshaking, so we need to + // wait until it's actually the case. + withContext(Dispatchers.Client) { + while (UPlayer.getPlayer() == null) { + delay(10) + } + } + + // Intentionally blocking the server thread, see big comment block above + runBlocking(Dispatchers.Client) { + // We pass `false` for `allowCheats` to ensure that not everybody can enable commands. + // This option by default will allow anyone to use operator commands, without being explicitly + // added as operator. + // TODO we do not want to actually set the gamemode via this method because it has pretty bad + // behavior, see the comment in the defaultGameMode effect + //#if MC>=11400 + //$$ val port = net.minecraft.util.HTTPUtil.getSuitableLanPort() + //$$ if (!server.shareToLAN(GameMode.Adventure.toMc(), false, port)) { + //$$ return@runBlocking + //$$ } + //#else + @Suppress("USELESS_ELVIS") // Forge applies an inappropriate NonNullByDefault + val portStr: String = server.shareToLAN(GameMode.Adventure.toMc(), false) ?: return@runBlocking + val port = Integer.parseInt(portStr) + //#endif + + // Simple Voice Chat documentation claims that by default it uses port 24454, but it seems they actually + // use the integrated server port by default. That's probably a good default as well + var voicePort = port + + // Plasmo Voice has 2 major versions, 1.x (using the modid plasmo_voice) and 2.x (using the modid plasmovoice) + if (ModLoaderUtil.isModLoaded("plasmo_voice")) { + // Plasmo 1.x documentation claims that it uses the server port by default, but it seems + // that they actually use 60606 for the integrated server. + voicePort = 60606 + } else if (ModLoaderUtil.isModLoaded("plasmovoice")) { + // Plasmo 2.x uses a random port, so we use their API to get the port. + val plasmoPort = PlasmoVoiceCompat.getPort() + if (plasmoPort.isPresent) { + voicePort = plasmoPort.get() + } + } + + serverPort.set(port) + thirdPartyVoicePort.set(voicePort) + } + } + } + } + + effect(refHolder) { + val whitelist = (whitelistSourceState() ?: return@effect)() + + whitelistUpdateJob?.cancel() + whitelistUpdateJob = server.coroutineScope.launch { + applyWhitelist(whitelist) + server.playerList.isWhiteListEnabled = true + } + } + + effect(refHolder) { + val ops = (opsSourceState() ?: return@effect)() + + opsUpdateJob?.cancel() + opsUpdateJob = server.coroutineScope.launch { + applyOps(ops) + } + } + + effect(refHolder) { + val resourcePack = (resourcePackSourceState() ?: return@effect)() + + server.coroutineScope.launch { + //#if MC>=11900 + //$$ appliedServerResourcePack = Optional.ofNullable(resourcePack) + //#else + server.setResourcePack(resourcePack?.url ?: "", resourcePack?.checksum ?: "") + //#endif + } + } + + effect(refHolder) { + val difficulty = (difficultySourceState() ?: return@effect)() + + server.coroutineScope.launch { + //#if MC>=11600 + //$$ server.setDifficultyForAllWorlds(difficulty.toMc(), true) + //#else + server.setDifficultyForAllWorlds(difficulty.toMc()) + //#endif + } + } + + effect(refHolder) { + val gameMode = (defaultGameModeSourceState() ?: return@effect)() + + server.coroutineScope.launch { + // TODO this doesn't set the default game mode (at least on 1.12.2) + // it sets the gamemode which is applied to everyone who joins, regardless of whether they've joined + // or even changed their gamemode before + //#if MC>=11700 + //$$ server.setDefaultGameMode(gameMode.toMc()) + //#elseif MC>=11600 + //$$ server.playerList.setGameType(gameMode.toMc()) + //#else + server.playerList.setGameType(gameMode.toMc()) + //#endif + } + } + + effect(refHolder) { + val cheatsEnabled = (cheatsEnabledSourceState() ?: return@effect)() + + server.coroutineScope.launch { + //#if MC>=11600 + //$$ appliedCheatsEnabled = cheatsEnabled + //#else + server.worlds.firstOrNull()?.worldInfo?.setAllowCommands(cheatsEnabled); + //#endif + } + } + + effect(refHolder) { + return@effect + } + + // TODO sync difficulty back when changed via vanilla menu or command + } + + override fun setOpenToLanSource(source: State) = openToLanSourceState.set(source.memo()) + override fun setWhitelistSource(source: State>) = whitelistSourceState.set(source.memo()) + override fun setOpsSource(source: State>) = opsSourceState.set(source.memo()) + override fun setResourcePackSource(source: State) = resourcePackSourceState.set(source.memo()) + override fun setDifficultySource(source: MutableState) = difficultySourceState.set(source.memo().withSetter { source.set(it) }) + override fun setDefaultGameModeSource(source: State) = defaultGameModeSourceState.set(source.memo()) + override fun setCheatsEnabledSource(source: State) = cheatsEnabledSourceState.set(source.memo()) + + override val whitelist: State?> = State { whitelistSourceState()?.invoke() } + + private suspend fun applyWhitelist(desiredWhitelist: Set) { + val whitelist = server.playerList.whitelistedPlayers + + // Add new players to the whitelist + for (uuid in desiredWhitelist) { + val name = UuidNameLookup.getName(uuid).asDeferred().await() + val profile = GameProfile(uuid, name) + @Suppress("SENSELESS_COMPARISON") // Forge applies an inappropriate NonNullByDefault + if (whitelist.getEntry(profile) == null) { + whitelist.addEntry(UserListWhitelistEntry(profile)) + } + } + + // Remove undesired players from the whitelist + for (userName in whitelist.keys) { + val profile = server.findProfileForName(userName) + if (profile != null && profile.id !in desiredWhitelist) { + whitelist.removeEntry(profile) + } + } + + // Kick anyone who is not on the whitelist + for (entity in (server.playerList as LanConnectionsAccessor).getPlayerEntityList()) { + if (entity.uniqueID !in desiredWhitelist) { + //#if MC>=11200 + entity.connection.disconnect( + UTextComponent(I18n.format("multiplayer.disconnect.server_shutdown")) + .component // need the MC one cause it cannot serialize the universal one + ) + //#else + //$$ entity.playerNetServerHandler.kickPlayerFromServer( + //$$ I18n.format("multiplayer.disconnect.server_shutdown") + //$$ ) + //#endif + } + } + } + + private suspend fun applyOps(desiredOps: Set) { + val playerList = server.playerList + val opList = playerList.oppedPlayers + + val allProfiles = opList.keys.mapNotNull { server.findProfileForName(it) } + + // Remove all players that are no longer op + for (profile in allProfiles) { + if (profile.id !in desiredOps) { + playerList.removeOp(profile) + } + } + + // Op all new players + for (uuid in desiredOps) { + val name = UuidNameLookup.getName(uuid).asDeferred().await() + val profile = GameProfile(uuid, name) + @Suppress("SENSELESS_COMPARISON") // Forge applies an inappropriate NonNullByDefault + if (opList.getEntry(profile) == null) { + playerList.addOp(profile) + } + } + } + + private fun IntegratedServer.findProfileForName(name: String): GameProfile? { + val userCache = playerProfileCache + //#if MC>=12000 + //$$ ?: error("userCache should not be null") // it's only nullable for TestServer + //#endif + //#if MC>=11700 + //$$ return userCache.findByName(name).orElse(null) + //#else + return userCache.getGameProfileForUsername(name) + //#endif + } + + // NOTE: Called from server main thread! + fun updateServerStatusResponse(statusJson: String) { + coroutineScope.launch { + mutableStatusResponseJson.set(statusJson) + } + } +} + +fun Difficulty.toMc(): McDifficulty = McDifficulty.getDifficultyEnum(ordinal) +fun GameMode.toMc(): McGameMode = McGameMode.getByID(ordinal) diff --git a/src/main/kotlin/gg/essential/util/ForkedJvm.kt b/src/main/kotlin/gg/essential/util/ForkedJvm.kt index 2881a20..81bb4aa 100644 --- a/src/main/kotlin/gg/essential/util/ForkedJvm.kt +++ b/src/main/kotlin/gg/essential/util/ForkedJvm.kt @@ -11,6 +11,8 @@ */ package gg.essential.util +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json import org.apache.logging.log4j.LogManager import java.io.Closeable import java.nio.file.Paths @@ -28,7 +30,7 @@ import kotlin.reflect.full.IllegalCallableAccessException class ForkedJvm(main: String, classpath: String? = null, jvmArgs: List = listOf()) : Closeable { val process: Process - constructor(main: Class<*>) : this(main.name) + constructor(main: Class<*>) : this(main.name, defaultClassPath(main)) init { val cmd = mutableListOf() @@ -71,13 +73,16 @@ class ForkedJvm(main: String, classpath: String? = null, jvmArgs: List = companion object { private val LOGGER = LogManager.getLogger(ForkedJvm::class.java) - fun defaultClassPath(): String = listOf( + fun defaultClassPath(vararg extraClasses: Class<*>): String = listOf( ForkedJvm::class.java, Unit::class.java, // kotlin-stdlib Class.forName("kotlin.io.path.PathsKt"), // kotlin-stdlib-jdk7 Class.forName("kotlin.collections.jdk8.CollectionsJDK8Kt"), // kotlin-stdlib-jdk8 IllegalCallableAccessException::class.java, // kotlin-reflect CoroutineContext::class.java, // kotlin-coroutines + Serializable::class.java, // kotlin-serialization-core + Json::class.java, // kotlin-serialization-json + *extraClasses, ).map { cls -> findCodeSource(cls) ?: throw UnsupportedOperationException("Failed to find $cls jar location") }.also { diff --git a/src/main/kotlin/gg/essential/util/GuiElementaPlatformImpl.kt b/src/main/kotlin/gg/essential/util/GuiElementaPlatformImpl.kt deleted file mode 100644 index a0e19b8..0000000 --- a/src/main/kotlin/gg/essential/util/GuiElementaPlatformImpl.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.util - -import gg.essential.config.AccessedViaReflection -import net.minecraft.client.renderer.GlStateManager - -@AccessedViaReflection("GuiElementaPlatform") -class GuiElementaPlatformImpl : GuiElementaPlatform { - override val isCoreProfile: Boolean = - //#if MC>=11700 - //$$ true - //#else - false - //#endif - - override fun glAlphaFunc(func: Int, ref: Float) { - //#if MC>=11700 - //$$ error("glAlphaFunc is not available on core profiles") - //#else - GlStateManager.alphaFunc(func, ref) - //#endif - } -} \ No newline at end of file diff --git a/src/main/kotlin/gg/essential/util/GuiEssentialPlatformImpl.kt b/src/main/kotlin/gg/essential/util/GuiEssentialPlatformImpl.kt index cbf832d..422f71d 100644 --- a/src/main/kotlin/gg/essential/util/GuiEssentialPlatformImpl.kt +++ b/src/main/kotlin/gg/essential/util/GuiEssentialPlatformImpl.kt @@ -17,9 +17,21 @@ import gg.essential.config.EssentialConfig import gg.essential.elementa.components.Window import gg.essential.event.client.ReAuthEvent import gg.essential.gui.common.modal.Modal +import gg.essential.gui.elementa.essentialmarkdown.EssentialMarkdown import gg.essential.gui.elementa.state.v2.MutableState +import gg.essential.gui.friends.message.v2.MessageRef +import gg.essential.gui.friends.state.IMessengerStates +import gg.essential.gui.friends.state.IRelationshipStates +import gg.essential.gui.friends.state.IStatusStates +import gg.essential.gui.friends.state.MessengerStateManagerImpl +import gg.essential.gui.friends.state.RelationshipStateManagerImpl +import gg.essential.gui.friends.state.SocialStates +import gg.essential.gui.friends.state.StatusStateManagerImpl +import gg.essential.gui.notification.NotificationsImpl +import gg.essential.gui.notification.NotificationsManager import gg.essential.gui.overlay.ModalManager import gg.essential.gui.overlay.ModalManagerImpl +import gg.essential.gui.overlay.OverlayManager import gg.essential.gui.overlay.OverlayManagerImpl import gg.essential.gui.util.onAnimationFrame import gg.essential.handlers.PauseMenuDisplay @@ -27,6 +39,8 @@ import gg.essential.model.backend.RenderBackend import gg.essential.model.backend.minecraft.MinecraftRenderBackend import gg.essential.model.util.Color import gg.essential.network.CMConnection +import gg.essential.network.connectionmanager.cosmetics.AssetLoader +import gg.essential.network.connectionmanager.notices.INoticesManager import gg.essential.universal.UImage import gg.essential.universal.UScreen import gg.essential.universal.utils.ReleasedDynamicTexture @@ -52,9 +66,18 @@ class GuiEssentialPlatformImpl : GuiEssentialPlatform { override val renderBackend: RenderBackend get() = MinecraftRenderBackend + override val overlayManager: OverlayManager + get() = OverlayManagerImpl + + override val assetLoader: AssetLoader + get() = Essential.getInstance().connectionManager.cosmeticsManager.assetLoader + override val cmConnection: CMConnection get() = Essential.getInstance().connectionManager + override val notifications: NotificationsManager + get() = NotificationsImpl + override fun createModalManager(): ModalManager { return ModalManagerImpl(OverlayManagerImpl) } @@ -115,6 +138,18 @@ class GuiEssentialPlatformImpl : GuiEssentialPlatform { override val pauseMenuDisplayWindow: Window get() = PauseMenuDisplay.window + override val mcProtocolVersion: Int + get() = MinecraftUtils.currentProtocolVersion + + override val mcGameVersion: String + //#if MC>=11600 + //$$ get() = net.minecraft.util.SharedConstants.getVersion().name + //#elseif MC>=11200 + get() = "1.12.2" + //#else + //$$ get() = "1.8.9" + //#endif + override fun currentServerType(): ServerType? { val minecraft = Minecraft.getMinecraft() val spsManager = Essential.getInstance().connectionManager.spsManager @@ -159,4 +194,26 @@ class GuiEssentialPlatformImpl : GuiEssentialPlatform { } }) } + + override fun createSocialStates(): SocialStates { + val cm = Essential.getInstance().connectionManager + return object : SocialStates { + override val relationships: IRelationshipStates by lazy { RelationshipStateManagerImpl(cm.relationshipManager) } + override val messages: IMessengerStates by lazy { MessengerStateManagerImpl(cm.chatManager) } + override val activity: IStatusStates by lazy { StatusStateManagerImpl(cm.profileManager, cm.spsManager) } + } + } + + override fun resolveMessageRef(messageRef: MessageRef) { + Essential.getInstance().connectionManager.chatManager.retrieveChannelHistoryUntil(messageRef) + } + + override val essentialUriListener: EssentialMarkdown.(EssentialMarkdown.LinkClickEvent) -> Unit + get() = gg.essential.util.essentialUriListener + + override val noticesManager: INoticesManager + get() = Essential.getInstance().connectionManager.noticesManager + + override val isOptiFineInstalled: Boolean + get() = OptiFineUtil.isLoaded() } \ No newline at end of file diff --git a/src/main/kotlin/gg/essential/util/MinecraftUtils.kt b/src/main/kotlin/gg/essential/util/MinecraftUtils.kt index bdf91f4..765ca4e 100644 --- a/src/main/kotlin/gg/essential/util/MinecraftUtils.kt +++ b/src/main/kotlin/gg/essential/util/MinecraftUtils.kt @@ -174,6 +174,7 @@ object MinecraftUtils : MinecraftUtils { hostAndPort: String, resourceMode: ServerData.ServerResourceMode = ServerData.ServerResourceMode.PROMPT, previousScreen: GuiScreen? = GuiMultiplayer(GuiMainMenu()), + showDisconnectWarning: Boolean = true, ) { val serverData = ServerData( serverName, @@ -186,7 +187,7 @@ object MinecraftUtils : MinecraftUtils { ) serverData.resourceMode = resourceMode - connectToServer(serverData, previousScreen) + connectToServer(serverData, previousScreen, showDisconnectWarning) } /** diff --git a/gui/elementa/src/main/kotlin/gg/essential/util/guiElementaPlatform.kt b/src/main/kotlin/gg/essential/util/PrettyIOException.kt similarity index 64% rename from gui/elementa/src/main/kotlin/gg/essential/util/guiElementaPlatform.kt rename to src/main/kotlin/gg/essential/util/PrettyIOException.kt index b2ed17f..600d963 100644 --- a/gui/elementa/src/main/kotlin/gg/essential/util/guiElementaPlatform.kt +++ b/src/main/kotlin/gg/essential/util/PrettyIOException.kt @@ -11,13 +11,11 @@ */ package gg.essential.util -interface GuiElementaPlatform { - val isCoreProfile: Boolean +import java.io.IOException - fun glAlphaFunc(func: Int, ref: Float) - - companion object { - internal val platform: GuiElementaPlatform = - Class.forName(GuiElementaPlatform::class.java.name + "Impl").getConstructor().newInstance() as GuiElementaPlatform +// Older MC uses toString to display the error on the "Failed to connect to server" screen +open class PrettyIOException(message: String, cause: Throwable? = null) : IOException(message, cause) { + override fun toString(): String { + return message!! } -} \ No newline at end of file +} diff --git a/src/main/kotlin/gg/essential/util/ResourceManagerUtil.kt b/src/main/kotlin/gg/essential/util/ResourceManagerUtil.kt index 3a2bdc8..489ae52 100644 --- a/src/main/kotlin/gg/essential/util/ResourceManagerUtil.kt +++ b/src/main/kotlin/gg/essential/util/ResourceManagerUtil.kt @@ -65,8 +65,10 @@ object ResourceManagerUtil : IResourceManagerReloadListener { //$$ override fun reload( //$$ stage: IStage, //$$ resourceManager: IResourceManager?, + //#if MC<12102 //$$ preparationsProfiler: IProfiler?, //$$ reloadProfiler: IProfiler?, + //#endif //$$ backgroundExecutor: Executor?, //$$ gameExecutor: Executor?, //$$ ): CompletableFuture { diff --git a/src/main/kotlin/gg/essential/util/extensions.kt b/src/main/kotlin/gg/essential/util/extensions.kt index 59f282b..10f982f 100644 --- a/src/main/kotlin/gg/essential/util/extensions.kt +++ b/src/main/kotlin/gg/essential/util/extensions.kt @@ -49,18 +49,14 @@ import net.minecraft.nbt.NBTTagCompound import net.minecraft.server.MinecraftServer import net.minecraft.server.integrated.IntegratedServer import net.minecraft.world.storage.WorldSummary -import org.apache.logging.log4j.Logger import java.awt.Color import java.io.File import java.io.InputStream import java.nio.file.Path import java.nio.file.Paths -import java.time.Duration -import java.time.Instant import java.util.* import java.util.concurrent.CompletableFuture import java.util.concurrent.Executor -import java.util.concurrent.TimeUnit import kotlin.io.path.div import kotlin.io.path.exists import kotlin.io.path.inputStream @@ -124,14 +120,6 @@ val Lazy.orNull: T? get() = if (isInitialized()) value else null fun List>.merge(): CompletableFuture> = CompletableFuture.allOf(*toTypedArray()).thenApply { map { it.join() } } -@JvmOverloads -fun CompletableFuture.logExceptions(logger: Logger = Essential.logger): CompletableFuture = - whenComplete { _, e -> - if (e != null) { - logger.error("Unhandled error:", e) - } - } - val MCMinecraft.executor get() = ext.executor val MinecraftServer.executor @@ -188,44 +176,6 @@ val globalEssentialDirectory: Path } } -private val weeksTimeMap = TreeMap( - mutableMapOf( - TimeUnit.DAYS.toMillis(7) to "w", - TimeUnit.DAYS.toMillis(1) to "d", - TimeUnit.HOURS.toMillis(1) to "h", - TimeUnit.MINUTES.toMillis(1) to "m", - TimeUnit.SECONDS.toMillis(1) to "s" - ) -) - -private val daysTimeMap = TreeMap( - mutableMapOf( - TimeUnit.DAYS.toMillis(1) to "d", - TimeUnit.HOURS.toMillis(1) to "h", - TimeUnit.MINUTES.toMillis(1) to "m", - TimeUnit.SECONDS.toMillis(1) to "s" - ) -) - -fun Instant.toCosmeticOptionTime(): String { - return Duration.between(Instant.now(), this).toShortString() -} - -fun Duration.toShortString(weeks: Boolean = true, expiredText: String = "Expired"): String { - val delta = toMillis() - val timeMap = if (weeks) weeksTimeMap else daysTimeMap - - val ceilEntry = timeMap.floorEntry(delta) ?: return expiredText - val floorEntry = timeMap.floorEntry(ceilEntry.key - 1) - if (ceilEntry === floorEntry || floorEntry == null) { - return (delta / ceilEntry.key).toString() + ceilEntry.value - } - val ceilUnits = delta / ceilEntry.key - val floorUnits = (delta - ceilUnits * ceilEntry.key) / floorEntry.key - - return ceilUnits.toString() + ceilEntry.value + " " + floorUnits.toString() + floorEntry.value -} - fun MCMinecraft.setSession(session: USession) { (this as MinecraftExt).setSession(session.toMC()) } diff --git a/src/main/kotlin/gg/essential/util/helpers.kt b/src/main/kotlin/gg/essential/util/helpers.kt index effeb32..9e3f010 100644 --- a/src/main/kotlin/gg/essential/util/helpers.kt +++ b/src/main/kotlin/gg/essential/util/helpers.kt @@ -13,8 +13,8 @@ package gg.essential.util import com.sparkuniverse.toolbox.util.DateTime import gg.essential.Essential -import gg.essential.config.EssentialConfig import gg.essential.config.LoadsResources +import gg.essential.config.McEssentialConfig import gg.essential.elementa.components.UIImage import gg.essential.elementa.components.Window import gg.essential.gui.common.ImageLoadCallback @@ -45,10 +45,6 @@ import java.nio.file.FileSystems import java.nio.file.Files import java.nio.file.Path import java.nio.file.StandardCopyOption -import java.time.LocalDate -import java.time.ZoneId -import java.time.format.DateTimeFormatter -import java.time.temporal.TemporalAccessor import java.util.* import java.util.concurrent.CompletableFuture import java.util.concurrent.ConcurrentHashMap @@ -82,28 +78,6 @@ inline fun loop(block: () -> Unit) { } } -const val DATE_FORMAT = "MMM dd, yyyy" -const val DATE_FORMAT_NO_YEAR = "MMM dd" - -fun getTimeFormat(includeSeconds: Boolean): String { - val seconds = if (includeSeconds) ":ss" else "" - return if (EssentialConfig.timeFormat == 0) "h:mm$seconds a" else "HH:mm$seconds" -} - -fun formatDate(date: LocalDate, displayYear: Boolean = true) = - date.formatter(if (displayYear) DATE_FORMAT else DATE_FORMAT_NO_YEAR) - -fun formatTime(time: TemporalAccessor, includeSeconds: Boolean) = - time.formatter(getTimeFormat(includeSeconds)) - -fun formatDateAndTime(date: TemporalAccessor) = - date.formatter("$DATE_FORMAT @ ${getTimeFormat(includeSeconds = false)}") - -fun TemporalAccessor.formatter(pattern: String): String { - val format = DateTimeFormatter.ofPattern(pattern, Locale.ENGLISH).withZone(ZoneId.systemDefault()) - return format.format(this) -} - private val jarFileCopies = ConcurrentHashMap() fun findCodeSource(javaClass: Class<*>): CodeSource? { @@ -522,7 +496,7 @@ val essentialUriListener: EssentialMarkdown.(EssentialMarkdown.LinkClickEvent) - val screenName = urlParts.getOrNull(0) when (screenName) { - "settings" -> GuiUtil.openScreen { EssentialConfig.gui(urlParts.getOrNull(1)) } + "settings" -> GuiUtil.openScreen { McEssentialConfig.gui(urlParts.getOrNull(1)) } "social" -> GuiUtil.openScreen { SocialMenu() } "minecraft" -> { when (urlParts.getOrNull(1)) { diff --git a/src/main/resources/assets/essential/commit.txt b/src/main/resources/assets/essential/commit.txt index 79b050c..87d3f5d 100644 --- a/src/main/resources/assets/essential/commit.txt +++ b/src/main/resources/assets/essential/commit.txt @@ -1 +1 @@ -311f9c466c \ No newline at end of file +840b07e568 \ No newline at end of file diff --git a/src/main/resources/assets/essential/textures/friends/group_blue.png b/src/main/resources/assets/essential/textures/friends/group_blue.png deleted file mode 100644 index 2c38a0d..0000000 Binary files a/src/main/resources/assets/essential/textures/friends/group_blue.png and /dev/null differ diff --git a/src/main/resources/assets/essential/textures/friends/group_green.png b/src/main/resources/assets/essential/textures/friends/group_green.png deleted file mode 100644 index cb0ed15..0000000 Binary files a/src/main/resources/assets/essential/textures/friends/group_green.png and /dev/null differ diff --git a/src/main/resources/assets/essential/textures/friends/group_purple.png b/src/main/resources/assets/essential/textures/friends/group_purple.png deleted file mode 100644 index 1614ac7..0000000 Binary files a/src/main/resources/assets/essential/textures/friends/group_purple.png and /dev/null differ diff --git a/src/main/resources/assets/essential/textures/friends/group_red.png b/src/main/resources/assets/essential/textures/friends/group_red.png deleted file mode 100644 index 9e61c6c..0000000 Binary files a/src/main/resources/assets/essential/textures/friends/group_red.png and /dev/null differ diff --git a/src/main/resources/assets/essential/textures/friends/group_yellow.png b/src/main/resources/assets/essential/textures/friends/group_yellow.png deleted file mode 100644 index b4fd990..0000000 Binary files a/src/main/resources/assets/essential/textures/friends/group_yellow.png and /dev/null differ diff --git a/src/main/resources/mixins.essential.json b/src/main/resources/mixins.essential.json index f620d45..88665f3 100644 --- a/src/main/resources/mixins.essential.json +++ b/src/main/resources/mixins.essential.json @@ -14,6 +14,7 @@ "client.Mixin_CancelVanillaScreenshotHotkey", "client.Mixin_EmoteWheelUnPressKeys", "client.Mixin_GuiKeyTypedEvent", + "client.Mixin_IncreaseMenuFpsLimit", "client.Mixin_RunEssentialTasks", "client.Mixin_ThreadTaskExecutor", "client.MixinMinecraft", @@ -50,6 +51,8 @@ "client.gui.ServerSelectionListAccessor", "client.gui.inventory.Mixin_DisableCosmeticsInInventory", "client.model.Mixin_ExtraTransform_CopyBetweenModelParts", + "client.model.Mixin_PlayerEntityRenderStateExt", + "client.model.Mixin_PlayerEntityRenderStateExt_UpdateRenderState", "client.model.ModelPlayerAccessor", "client.model.geom.Mixin_ExtraTransform_ModelPart", "client.multiplayer.MixinGuiConnecting_SetMostRecentlyConnectedMultiplayerServer", @@ -57,6 +60,7 @@ "client.network.CPacketChatMessageAccessor", "client.network.Mixin_JoinEvent", "client.network.Mixin_LogExceptions", + "client.network.Mixin_OverridePing", "client.network.Mixin_RedirectToLocalConnection", "client.network.Mixin_RefreshSkinOnChange", "client.network.Mixin_RegisterEssentialChannel", @@ -89,12 +93,15 @@ "client.renderer.entity.MixinRender", "client.renderer.entity.MixinRendererLivingEntity", "client.renderer.entity.MixinRenderPlayer", + "client.renderer.entity.equipment.EquipmentRendererAccessor", "client.resources.SkinProviderFileCacheAccessor", "client.resources.PlayerSkinProviderAccessorMixin", "client.resources.Mixin_InstantSkinLoad", - "client.settings.Mixin_UnbindConflictingKeybinds", + "client.settings.Mixin_UnbindConflictingKeybinds_OnLoad", + "client.settings.Mixin_UnbindConflictingKeybinds_OnChange", "client.settings.MixinKeyBinding", "client.shader.MixinFramebuffer", + "compatibility.emf.Mixin_EMFAnimationEntityContext", "compatibility.fancymenu.FancyMenuScreenBackgroundRenderedEventAcc", "compatibility.fancymenu.KonkreteDrawScreenEventAcc", "compatibility.fancymenu.KonkreteGuiScreenEventAcc", @@ -119,6 +126,7 @@ "compatibility.torohealth.Mixin_EmulatedPlayerCompat", "compatibility.vanilla.Mixin_FixArrowsStuckInUnusedPlayerModelPart", "compatibility.vanilla.Mixin_FixBufferBuilderSortReset", + "compatibility.vanilla.Mixin_FixKeyboardNavigation", "compatibility.vanilla.Mixin_FixPacketHandlingPastServerShutdown", "compatibility.vanilla.Mixin_FixResourcePackCrash", "compatibility.vanilla.Mixin_FixSelfieNameplateOrientation", @@ -137,8 +145,10 @@ "events.Mixin_GuiClickEvent_Priority", "events.Mixin_GuiMouseReleaseEvent", "events.Mixin_GuiDrawScreenEvent_Priority", + "events.Mixin_GuiDrawScreenEvent_Priority_LoadingScreen", "events.Mixin_MouseScrollEvent", "events.Mixin_RenderTickEvent", + "events.Mixin_RenderTickEvent_LoadingScreen", "feature.chat.Mixin_ChatPeekScrolling", "feature.cosmetics.Mixin_CutoutCapeTextures", "feature.cosmetics.Mixin_UpdateOffscreenPlayers", @@ -217,9 +227,12 @@ "server.MinecraftServerMixin_PvPGameRule", "server.Mixin_ControlAreCommandsAllowed", "server.Mixin_InvertPlayerDataPriority", + "server.Mixin_ServerCoroutineScope", + "server.Mixin_ServerCoroutineScope_IntegratedServer", "server.Mixin_PublishServerStatusResponse", "server.integrated.LanConnectionsAccessor", "server.integrated.Mixin_FixDefaultOpPermissionLevel", + "server.integrated.Mixin_IntegratedServerManager", "server.integrated.MixinIntegratedServer", "util.MixinTabCompleter", "util.Mixin_CaptureScreenshots", diff --git a/src/main/resources/mixins.essential.tests.json b/src/main/resources/mixins.essential.tests.json new file mode 100644 index 0000000..a26e830 --- /dev/null +++ b/src/main/resources/mixins.essential.tests.json @@ -0,0 +1,14 @@ +{ + "required": true, + "minVersion": "0.8", + //#if MC>=11700 + //$$ "compatibilityLevel": "JAVA_16", + //#else + "compatibilityLevel": "JAVA_8", + //#endif + "package": "gg.essential.mixins.tests", + "plugin": "gg.essential.mixins.IntegrationTestsPlugin", + "refmap": "mixins.essential.refmap.json", + "mixins": [ + ] +} diff --git a/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/cosmetics/skinmask/SkinMask.kt b/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/cosmetics/skinmask/SkinMask.kt index bd16fd2..ab87b58 100644 --- a/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/cosmetics/skinmask/SkinMask.kt +++ b/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/cosmetics/skinmask/SkinMask.kt @@ -123,7 +123,7 @@ class SkinMask(val parts: Map) { else -> { val combined = masks.first().mutableCopy() for (i in 1..masks.lastIndex) { - combined.setOr(masks[i]) + combined.setAnd(masks[i]) } part to combined } diff --git a/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/mod/cosmetics/CosmeticBundle.kt b/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/mod/cosmetics/CosmeticBundle.kt index 89ccc2c..b5daccd 100644 --- a/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/mod/cosmetics/CosmeticBundle.kt +++ b/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/mod/cosmetics/CosmeticBundle.kt @@ -22,6 +22,7 @@ data class CosmeticBundle( val name: String, val tier: CosmeticTier, val discountPercent: Float, + val rotateOnPreview: Boolean = false, var skin: Skin, val cosmetics: Map, val settings: Map>, diff --git a/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/mod/cosmetics/EmoteWheelPage.kt b/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/mod/cosmetics/EmoteWheelPage.kt index 85020a7..610b2bf 100644 --- a/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/mod/cosmetics/EmoteWheelPage.kt +++ b/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/mod/cosmetics/EmoteWheelPage.kt @@ -11,11 +11,15 @@ */ package gg.essential.mod.cosmetics +import gg.essential.cosmetics.CosmeticId import gg.essential.model.util.Instant data class EmoteWheelPage( val id: String, val createdAt: Instant, - var isSelected: Boolean, - val slots: MutableList, -) \ No newline at end of file + val slots: List, +) { + companion object { + const val SLOTS = 8 + } +} \ No newline at end of file diff --git a/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/mod/cosmetics/database/GitRepoCosmeticsDatabase.kt b/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/mod/cosmetics/database/GitRepoCosmeticsDatabase.kt index f6b9639..a839624 100644 --- a/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/mod/cosmetics/database/GitRepoCosmeticsDatabase.kt +++ b/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/mod/cosmetics/database/GitRepoCosmeticsDatabase.kt @@ -498,6 +498,7 @@ class GitRepoCosmeticsDatabase( bundle.name, bundle.tier, bundle.discountPercent, + bundle.rotateOnPreview, bundle.skin, bundle.cosmetics, bundle.settings, @@ -840,6 +841,8 @@ class GitRepoCosmeticsDatabase( val name: String, val tier: CosmeticTier, val discount: Float, + @SerialName("rotate_on_preview") + val rotateOnPreview: Boolean = false, var skin: CosmeticBundle.Skin, val cosmetics: Map, val settings: Map>, @@ -1053,6 +1056,7 @@ private suspend fun FileAccess.loadBundle(metadataFile: Path): CosmeticBundle { metadata.name, metadata.tier, metadata.discount, + metadata.rotateOnPreview, metadata.skin, metadata.cosmetics, metadata.settings diff --git a/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/mod/cosmetics/preview/PerspectiveCamera.kt b/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/mod/cosmetics/preview/PerspectiveCamera.kt index ab22af9..9d8800e 100644 --- a/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/mod/cosmetics/preview/PerspectiveCamera.kt +++ b/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/mod/cosmetics/preview/PerspectiveCamera.kt @@ -61,7 +61,7 @@ data class PerspectiveCamera(val camera: Vec3, val target: Vec3, val fov: Float) CosmeticSlot.ICON -> Vector3(34.6, 50.5, -40) to Vector3(0, 30, 0) CosmeticSlot.TOP -> Vector3(34.7, 36.2, -39.5) to Vector3(2.6, 17.0, -2.5) CosmeticSlot.ACCESSORY -> Vector3(34.7, 36.2, -39.5) to Vector3(2.6, 17.0, -2.5) - CosmeticSlot.FULL_BODY -> Vector3(73.3, 20.7, -81.3) to Vector3(3.7, 18.2, -2.8) + CosmeticSlot.FULL_BODY, CosmeticSlot.PET -> Vector3(73.3, 20.7, -81.3) to Vector3(3.7, 18.2, -2.8) // These have a camera config but no cosmetics yet // CosmeticSlot.RIDEABLE -> Vector3(73.3, 20.7, -81.3) to Vector3(3.7, 19.2, -2.8), diff --git a/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/mod/cosmetics/settings/CosmeticProperty.kt b/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/mod/cosmetics/settings/CosmeticProperty.kt index b537291..f562b0f 100644 --- a/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/mod/cosmetics/settings/CosmeticProperty.kt +++ b/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/mod/cosmetics/settings/CosmeticProperty.kt @@ -11,6 +11,7 @@ */ package gg.essential.mod.cosmetics.settings +import gg.essential.mod.cosmetics.CosmeticSlot import gg.essential.model.Side import gg.essential.model.util.Color import gg.essential.model.util.ColorAsRgbSerializer @@ -309,6 +310,23 @@ sealed class CosmeticProperty { ) } + @SerialName("MUTUALLY_EXCLUSIVE") + @Serializable + data class MutuallyExclusive( + override val id: String?, + override val enabled: Boolean, + val data: Data + ) : CosmeticProperty() { + + @Transient + override val type: CosmeticPropertyType = CosmeticPropertyType.MUTUALLY_EXCLUSIVE + + @Serializable + data class Data( + val slots: Set = emptySet(), + ) + } + object TheSerializer : FallbackPolymorphicSerializer(CosmeticProperty::class, "type", "__type", "__unknown__") { override val module = SerializersModule { polymorphic(CosmeticProperty::class) { @@ -324,6 +342,7 @@ sealed class CosmeticProperty { subclass(TransitionDelay::class, TransitionDelay.serializer()) subclass(Variants::class, Variants.serializer()) subclass(DefaultSide::class, DefaultSide.serializer()) + subclass(MutuallyExclusive::class, MutuallyExclusive.serializer()) } } } diff --git a/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/mod/cosmetics/settings/CosmeticPropertyType.kt b/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/mod/cosmetics/settings/CosmeticPropertyType.kt index 2589d36..6bf5723 100644 --- a/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/mod/cosmetics/settings/CosmeticPropertyType.kt +++ b/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/mod/cosmetics/settings/CosmeticPropertyType.kt @@ -28,4 +28,5 @@ enum class CosmeticPropertyType( TRANSITION_DELAY("Transition Delay", true), VARIANTS("Variants", true), DEFAULT_SIDE("Default Side", true), + MUTUALLY_EXCLUSIVE("Mutually Exclusive", true), } diff --git a/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/model/ParticleSystem.kt b/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/model/ParticleSystem.kt index ac225d2..486e4d3 100644 --- a/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/model/ParticleSystem.kt +++ b/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/model/ParticleSystem.kt @@ -58,6 +58,7 @@ import gg.essential.model.util.UMatrixStack import gg.essential.model.util.UVertexConsumer import gg.essential.model.util.rotateBy import gg.essential.model.util.rotateSelfBy +import java.util.UUID import kotlin.math.PI import kotlin.math.absoluteValue import kotlin.math.sqrt @@ -245,6 +246,8 @@ class ParticleSystem( cameraPos: Vec3, cameraRot: Quaternion, particleVertexConsumerProvider: VertexConsumerProvider, + cameraUuid: UUID, + cameraFirstPerson: Boolean, ) { val cameraFacing = vec3(0f, 0f, -1f).rotateBy(cameraRot) for ((renderPass, particles) in billboardRenderPasses.entries.sortedBy { it.key.material.needsSorting }) { @@ -257,12 +260,12 @@ class ParticleSystem( particle.distance = cameraPos.minus(particle.billboardPosition).dot(billboardNormal) } for (particle in particles.sortedByDescending { it.distance }) { - particle.renderBillboard(matrixStack, vertexConsumer, cameraFacing) + particle.renderBillboard(matrixStack, vertexConsumer, cameraFacing, cameraUuid, cameraFirstPerson) } } else { for (particle in particles) { particle.prepareBillboard(cameraPos, cameraRot) - particle.renderBillboard(matrixStack, vertexConsumer, cameraFacing) + particle.renderBillboard(matrixStack, vertexConsumer, cameraFacing, cameraUuid, cameraFirstPerson) } } } @@ -967,7 +970,18 @@ class ParticleSystem( * * @throws UnsupportedOperationException if the particle does not have a billboard component */ - fun renderBillboard(matrixStack: UMatrixStack, vertexConsumer: UVertexConsumer, cameraFacing: Vec3) { + fun renderBillboard( + matrixStack: UMatrixStack, + vertexConsumer: UVertexConsumer, + cameraFacing: Vec3, + cameraUuid: UUID, + cameraFirstPerson: Boolean, + ) { + // Abide by the particle effect visibility component for the current player. + if (cameraUuid == emitter.sourceEntity.uuid) { + if (!components.particleVisibility.let { if (cameraFirstPerson) it.firstPerson else it.thirdPerson }) return + } + val appearance = components.particleAppearanceBillboard ?: throw UnsupportedOperationException() components.particleInitialization?.perRenderExpression?.eval(molang) diff --git a/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/model/backend/PlayerPose.kt b/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/model/backend/PlayerPose.kt index e60ce34..9895cc7 100644 --- a/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/model/backend/PlayerPose.kt +++ b/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/model/backend/PlayerPose.kt @@ -13,7 +13,12 @@ package gg.essential.model.backend import dev.folomeev.kotgl.matrix.matrices.Mat4 import dev.folomeev.kotgl.matrix.vectors.Vec3 +import dev.folomeev.kotgl.matrix.vectors.vec3 +import dev.folomeev.kotgl.matrix.vectors.vecUnitX +import dev.folomeev.kotgl.matrix.vectors.vecUnitY +import dev.folomeev.kotgl.matrix.vectors.vecUnitZ import gg.essential.model.EnumPart +import gg.essential.model.util.Quaternion import kotlin.math.PI data class PlayerPose( @@ -75,6 +80,14 @@ data class PlayerPose( pivotZ = pivotZ + pivotOffset.z, ) + val pivot: Vec3 + get() = vec3(pivotX, pivotY, pivotZ) + + val rotation: Quaternion + get() = Quaternion.fromAxisAngle(vecUnitZ(), rotateAngleZ) * + Quaternion.fromAxisAngle(vecUnitY(), rotateAngleY) * + Quaternion.fromAxisAngle(vecUnitX(), rotateAngleX) + companion object { // Parts that weren't rendered, we'll just draw far away so they'll appear is if they weren't there val MISSING = Part(pivotY = -10000f) diff --git a/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/model/file/ParticlesFile.kt b/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/model/file/ParticlesFile.kt index 8c45154..5a49b9b 100644 --- a/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/model/file/ParticlesFile.kt +++ b/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/model/file/ParticlesFile.kt @@ -224,6 +224,8 @@ data class ParticleEffectComponents( val particleMotionParametric: ParticleMotionParametric? = null, @SerialName("minecraft:particle_lifetime_expression") val particleLifetimeExpression: ParticleLifetimeExpression? = null, + @SerialName("essential:particle_visibility") + val particleVisibility: ParticleVisibility = ParticleVisibility(), ) { /** * This component specifies the frame of reference of the emitter. @@ -657,6 +659,17 @@ data class ParticleEffectComponents( @SerialName("max_lifetime") val maxLifetime: MolangExpression, ) + + /** Custom component that controls particle visibility for particles emitted by the player. Does not affect visibility for particles emitted by other players */ + @Serializable + data class ParticleVisibility( + /** controls whether player's own particles are seen in first-person */ + @SerialName("first_person") + val firstPerson: Boolean = true, + /** controls whether player's own particles are seen in third-person */ + @SerialName("third_person") + val thirdPerson: Boolean = true, + ) } @Serializable(with = MolangColorOrGradientSerializer::class) diff --git a/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/model/util/kotgl.kt b/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/model/util/kotgl.kt index 30f719e..09961c3 100644 --- a/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/model/util/kotgl.kt +++ b/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/model/util/kotgl.kt @@ -15,6 +15,8 @@ import dev.folomeev.kotgl.matrix.matrices.Mat3 import dev.folomeev.kotgl.matrix.matrices.Mat4 import dev.folomeev.kotgl.matrix.matrices.mat3 import dev.folomeev.kotgl.matrix.matrices.mat4 +import dev.folomeev.kotgl.matrix.matrices.mutables.MutableMat3 +import dev.folomeev.kotgl.matrix.matrices.mutables.mutableMat3 import dev.folomeev.kotgl.matrix.vectors.Vec3 import dev.folomeev.kotgl.matrix.vectors.Vec4 import dev.folomeev.kotgl.matrix.vectors.mutables.MutableVec3 @@ -23,6 +25,9 @@ import dev.folomeev.kotgl.matrix.vectors.mutables.mutableVec3 import dev.folomeev.kotgl.matrix.vectors.mutables.set import dev.folomeev.kotgl.matrix.vectors.vec3 import dev.folomeev.kotgl.matrix.vectors.vec4 +import dev.folomeev.kotgl.matrix.vectors.vecUnitX +import dev.folomeev.kotgl.matrix.vectors.vecUnitY +import dev.folomeev.kotgl.matrix.vectors.vecUnitZ import kotlin.math.asin import kotlin.math.atan2 @@ -79,6 +84,13 @@ fun Vec3.rotateBy(q: Quaternion) = rotateBy(q, ::mutableVec3) */ fun MutableVec3.rotateSelfBy(q: Quaternion) = rotateBy(q, ::set) +fun Quaternion.toMat3(): MutableMat3 { + val x = vecUnitX().rotateBy(this) + val y = vecUnitY().rotateBy(this) + val z = vecUnitZ().rotateBy(this) + return mutableMat3(x.x, y.x, z.x, x.y, y.y, z.y, x.z, y.z, z.z) +} + fun Mat4.getRotationEulerZYX() = vec3( // diff --git a/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/network/cosmetics/Cosmetic.kt b/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/network/cosmetics/Cosmetic.kt index a78d775..fa927d9 100644 --- a/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/network/cosmetics/Cosmetic.kt +++ b/subprojects/cosmetics/src/commonMain/kotlin/gg/essential/network/cosmetics/Cosmetic.kt @@ -16,6 +16,7 @@ package gg.essential.network.cosmetics import gg.essential.mod.EssentialAsset import gg.essential.mod.Model import gg.essential.mod.cosmetics.CosmeticAssets +import gg.essential.mod.cosmetics.CosmeticSlot import gg.essential.mod.cosmetics.CosmeticTier import gg.essential.mod.cosmetics.CosmeticType import gg.essential.mod.cosmetics.SkinLayer @@ -97,6 +98,9 @@ data class Cosmetic( val defaultSide: Side? get() = property()?.data?.side + val mutuallyExclusiveWith: Set + get() = property()?.data?.slots ?: emptySet() + // Added some convenient variants values, especially convenient in Java val variants: List? get() = property()?.data?.variants diff --git a/subprojects/feature-flags/src/main/kotlin/gg/essential/config/HideIfDisabled.kt b/subprojects/feature-flags/src/main/kotlin/gg/essential/config/HideIfDisabled.kt index 5bc4cf5..8cf32b5 100644 --- a/subprojects/feature-flags/src/main/kotlin/gg/essential/config/HideIfDisabled.kt +++ b/subprojects/feature-flags/src/main/kotlin/gg/essential/config/HideIfDisabled.kt @@ -22,6 +22,7 @@ package gg.essential.config * The only purpose of this annotation is to hide code which should not yet be visible to the general public. */ @Target( + AnnotationTarget.FILE, AnnotationTarget.CLASS, AnnotationTarget.CONSTRUCTOR, AnnotationTarget.FUNCTION, diff --git a/subprojects/ice/src/main/kotlin/gg/essential/ice/stun/StunManager.kt b/subprojects/ice/src/main/kotlin/gg/essential/ice/stun/StunManager.kt index cbd4cda..14dba6c 100644 --- a/subprojects/ice/src/main/kotlin/gg/essential/ice/stun/StunManager.kt +++ b/subprojects/ice/src/main/kotlin/gg/essential/ice/stun/StunManager.kt @@ -57,7 +57,7 @@ class StunManager(private val scope: CoroutineScope) { try { job.join() // wait some extra time for in-flight packets (we don't want to warn about them being unexpected) - delay(3.seconds) + delay(15.seconds) } finally { servers.remove(ufrag) server.knownRemotes.forEach { remoteToServer.remove(it, server) } diff --git a/subprojects/ice/src/main/kotlin/gg/essential/ice/stun/StunSocket.kt b/subprojects/ice/src/main/kotlin/gg/essential/ice/stun/StunSocket.kt index c6e0642..297bd87 100644 --- a/subprojects/ice/src/main/kotlin/gg/essential/ice/stun/StunSocket.kt +++ b/subprojects/ice/src/main/kotlin/gg/essential/ice/stun/StunSocket.kt @@ -30,6 +30,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.plus import kotlinx.coroutines.yield import org.slf4j.Logger +import java.io.IOException import java.net.BindException import java.net.DatagramPacket import java.net.DatagramSocket @@ -104,6 +105,7 @@ class StunSocket( } catch (e: Exception) { if (e is SocketException && e.message?.startsWith("Network is unreachable:") == true || e is BindException && e.message == "Cannot assign requested address: no further information" + || e is IOException && e.message == "Network is unreachable (sendto failed)" || e is NoRouteToHostException) { logger.trace("Failed to send to {}: {}", packet.socketAddress, e.message) knownUnreachable.add(packet.address) diff --git a/subprojects/infra/src/main/java/gg/essential/connectionmanager/common/packet/notices/ClientNoticeDismissPacket.java b/subprojects/infra/src/main/java/gg/essential/connectionmanager/common/packet/notices/ClientNoticeBulkDismissPacket.java similarity index 68% rename from subprojects/infra/src/main/java/gg/essential/connectionmanager/common/packet/notices/ClientNoticeDismissPacket.java rename to subprojects/infra/src/main/java/gg/essential/connectionmanager/common/packet/notices/ClientNoticeBulkDismissPacket.java index d47a4c9..8ea5bcb 100644 --- a/subprojects/infra/src/main/java/gg/essential/connectionmanager/common/packet/notices/ClientNoticeDismissPacket.java +++ b/subprojects/infra/src/main/java/gg/essential/connectionmanager/common/packet/notices/ClientNoticeBulkDismissPacket.java @@ -11,21 +11,23 @@ */ package gg.essential.connectionmanager.common.packet.notices; -import gg.essential.lib.gson.annotations.SerializedName; import gg.essential.connectionmanager.common.packet.Packet; +import gg.essential.lib.gson.annotations.SerializedName; import org.jetbrains.annotations.NotNull; -public class ClientNoticeDismissPacket extends Packet { +import java.util.Set; + +public class ClientNoticeBulkDismissPacket extends Packet { - @SerializedName("a") - private final @NotNull String id; + @SerializedName("notice_ids") + private final @NotNull Set noticeIds; - public ClientNoticeDismissPacket(final @NotNull String id) { - this.id = id; + public ClientNoticeBulkDismissPacket(@NotNull Set noticeIds) { + this.noticeIds = noticeIds; } - public @NotNull String getId() { - return this.id; + public @NotNull Set getNoticeIds() { + return noticeIds; } } diff --git a/subprojects/infra/src/main/java/gg/essential/connectionmanager/common/packet/notices/ServerNoticeBulkDismissPacket.java b/subprojects/infra/src/main/java/gg/essential/connectionmanager/common/packet/notices/ServerNoticeBulkDismissPacket.java new file mode 100644 index 0000000..3e6b783 --- /dev/null +++ b/subprojects/infra/src/main/java/gg/essential/connectionmanager/common/packet/notices/ServerNoticeBulkDismissPacket.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.connectionmanager.common.packet.notices; + + +import gg.essential.connectionmanager.common.packet.Packet; +import gg.essential.lib.gson.annotations.SerializedName; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.Set; + +public class ServerNoticeBulkDismissPacket extends Packet { + + @SerializedName("dismissed_notice_ids") + private final @NotNull Set noticeIds; + + @SerializedName("errors") + private final @NotNull List errors; + + public ServerNoticeBulkDismissPacket(final @NotNull Set noticeIds, final @NotNull List errors) { + this.noticeIds = noticeIds; + this.errors = errors; + } + + public @NotNull Set getNoticeIds() { + return this.noticeIds; + } + + public @NotNull List getErrors() { + return this.errors; + } + + public static class ErrorDetails { + + @SerializedName("notice_id") + private final @NotNull String noticeId; + + @SerializedName("reason") + private final @NotNull String reason; + + public ErrorDetails(final @NotNull String noticeId, final @NotNull String reason) { + this.noticeId = noticeId; + this.reason = reason; + } + + public @NotNull String getNoticeId() { + return noticeId; + } + + public @NotNull String getReason() { + return reason; + } + + } + +} \ No newline at end of file diff --git a/subprojects/infra/src/main/java/gg/essential/cosmetics/model/Cosmetic.java b/subprojects/infra/src/main/java/gg/essential/cosmetics/model/Cosmetic.java index 5b9c2a7..dd8bed2 100644 --- a/subprojects/infra/src/main/java/gg/essential/cosmetics/model/Cosmetic.java +++ b/subprojects/infra/src/main/java/gg/essential/cosmetics/model/Cosmetic.java @@ -22,7 +22,6 @@ import org.jetbrains.annotations.Nullable; import java.util.Collections; -import java.util.List; import java.util.Map; import java.util.Set; @@ -37,18 +36,6 @@ public class Cosmetic implements DisplayNameHolder, PriceHolder, SkinLayersHolde @SerializedName("c") private final @NotNull Map<@NotNull String, @NotNull String> displayNames; - @SerializedName("d") - private final @Nullable CosmeticAssets assets; - - /** - * @deprecated Use {@link CosmeticAssets#getSettings()} instead. - * This field will be removed after users have migrated to `release/1.2` otherwise users on an old - * client version won't get any `settings` sent to them until they restart their game. - */ - @Deprecated - @SerializedName("e") - private @Nullable List<@NotNull CosmeticSetting> settings; - @SerializedName("f") private final int storePackageId; @@ -83,14 +70,12 @@ public class Cosmetic implements DisplayNameHolder, PriceHolder, SkinLayersHolde private @Nullable CosmeticTier tier; @SerializedName("q") - private @Nullable Map<@NotNull String, @NotNull EssentialAsset> assetsMap; + private @NotNull Map<@NotNull String, @NotNull EssentialAsset> assetsMap; public Cosmetic( final @NotNull String id, final @NotNull String type, final @NotNull Map<@NotNull String, @NotNull String> displayNames, - final @Nullable CosmeticAssets assets, - final @Nullable @Deprecated List<@NotNull CosmeticSetting> settings, final int storePackageId, final @Nullable Map<@NotNull String, @NotNull Double> prices, final @Nullable Set<@NotNull String> tags, @@ -102,13 +87,11 @@ public Cosmetic( final @Nullable Integer defaultSortWeight, final @Nullable Integer priceCoins, final @Nullable CosmeticTier tier, - final @Nullable Map<@NotNull String, @NotNull EssentialAsset> assetsMap + final @NotNull Map<@NotNull String, @NotNull EssentialAsset> assetsMap ) { this.id = id; this.type = type; this.displayNames = displayNames; - this.assets = assets; - this.settings = settings; this.storePackageId = storePackageId; this.prices = prices; this.tags = tags; @@ -136,24 +119,6 @@ public Cosmetic( return this.displayNames; } - public @Nullable CosmeticAssets getAssets() { - return this.assets; - } - - /** - * @deprecated Use {@link Cosmetic#getAssets()} and {@link CosmeticAssets#getSettings()} instead. - * This field will be removed after users have migrated to `release/1.2` otherwise users on an old - * client version won't get any `settings` sent to them until they restart their game. - */ - @Deprecated - public @NotNull List<@NotNull CosmeticSetting> getSettings() { - if (this.settings == null) { - this.settings = Collections.emptyList(); - } - - return this.settings; - } - public int getStorePackageId() { return this.storePackageId; } @@ -223,7 +188,7 @@ public boolean isAvailableAt(final @NotNull DateTime dateTime) { return tier; } - public @Nullable Map<@NotNull String, @NotNull EssentialAsset> getAssetsMap() { + public @NotNull Map<@NotNull String, @NotNull EssentialAsset> getAssetsMap() { return assetsMap; } } diff --git a/subprojects/infra/src/main/java/gg/essential/cosmetics/model/CosmeticStoreBundle.java b/subprojects/infra/src/main/java/gg/essential/cosmetics/model/CosmeticStoreBundle.java index d622bc5..4f9999e 100644 --- a/subprojects/infra/src/main/java/gg/essential/cosmetics/model/CosmeticStoreBundle.java +++ b/subprojects/infra/src/main/java/gg/essential/cosmetics/model/CosmeticStoreBundle.java @@ -29,25 +29,29 @@ public class CosmeticStoreBundle { private final float discount; + private final boolean rotateOnPreview; + private final @NotNull Map<@NotNull CosmeticSlot, @NotNull String> cosmetics; private final @NotNull Map<@NotNull String, @NotNull List<@NotNull CosmeticSetting>> settings; public CosmeticStoreBundle( - @NotNull String id, - @NotNull String name, - @NotNull CosmeticStoreBundleSkin skin, - @NotNull CosmeticTier tier, - float discount, - @NotNull Map<@NotNull CosmeticSlot, @NotNull String> cosmetics, - @NotNull Map<@NotNull String, @NotNull List<@NotNull CosmeticSetting>> settings + @NotNull String id, + @NotNull String name, + @NotNull CosmeticStoreBundleSkin skin, + @NotNull CosmeticTier tier, + float discount, + boolean rotateOnPreview, + @NotNull Map<@NotNull CosmeticSlot, @NotNull String> cosmetics, + @NotNull Map<@NotNull String, @NotNull List<@NotNull CosmeticSetting>> settings ) { this.id = id; this.name = name; this.skin = skin; this.tier = tier; this.discount = discount; + this.rotateOnPreview = rotateOnPreview; this.cosmetics = cosmetics; this.settings = settings; } @@ -72,6 +76,10 @@ public float getDiscount() { return discount; } + public boolean getRotateOnPreview() { + return rotateOnPreview; + } + public @NotNull Map<@NotNull CosmeticSlot, @NotNull String> getCosmetics() { return cosmetics; } diff --git a/src/main/java/gg/essential/network/connectionmanager/NetworkedManager.java b/subprojects/infra/src/main/java/gg/essential/network/connectionmanager/NetworkedManager.java similarity index 100% rename from src/main/java/gg/essential/network/connectionmanager/NetworkedManager.java rename to subprojects/infra/src/main/java/gg/essential/network/connectionmanager/NetworkedManager.java diff --git a/subprojects/infra/src/main/java/gg/essential/network/connectionmanager/notices/INoticesManager.kt b/subprojects/infra/src/main/java/gg/essential/network/connectionmanager/notices/INoticesManager.kt new file mode 100644 index 0000000..4f79282 --- /dev/null +++ b/subprojects/infra/src/main/java/gg/essential/network/connectionmanager/notices/INoticesManager.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.network.connectionmanager.notices + +import gg.essential.notices.model.Notice + +interface INoticesManager { + /** Note: For internal / testing use only. */ + fun populateNotices(notices: Collection) + /** Note: For internal / testing use only. */ + fun removeNotices(notices: Set) + + fun dismissNotice(id: String) +} diff --git a/src/main/kotlin/gg/essential/config/GuiFactory.kt b/subprojects/infra/src/main/java/gg/essential/network/connectionmanager/notices/NoticeListener.java similarity index 55% rename from src/main/kotlin/gg/essential/config/GuiFactory.kt rename to subprojects/infra/src/main/java/gg/essential/network/connectionmanager/notices/NoticeListener.java index 0a5c331..a4ba3be 100644 --- a/src/main/kotlin/gg/essential/config/GuiFactory.kt +++ b/subprojects/infra/src/main/java/gg/essential/network/connectionmanager/notices/NoticeListener.java @@ -9,16 +9,17 @@ * commercialize, or otherwise exploit, or create derivative works based * upon, this file or any other in this repository, all of which is reserved by Essential. */ -package gg.essential.config +package gg.essential.network.connectionmanager.notices; -import gg.essential.gui.elementa.state.v2.ListState -import gg.essential.gui.vigilancev2.VigilanceV2SettingsGui -import gg.essential.vigilance.data.PropertyData +import gg.essential.notices.model.Notice; -class GuiFactory internal constructor( - private val properties: ListState, -) { - operator fun invoke(category: String? = null): VigilanceV2SettingsGui { - return VigilanceV2SettingsGui(properties, category) +interface NoticeListener { + void noticeAdded(Notice notice); + + void noticeRemoved(Notice notice); + + void onConnect(); + + default void resetState() { } } diff --git a/src/main/java/gg/essential/network/connectionmanager/relationship/FriendRequestState.java b/subprojects/infra/src/main/java/gg/essential/network/connectionmanager/relationship/FriendRequestState.java similarity index 100% rename from src/main/java/gg/essential/network/connectionmanager/relationship/FriendRequestState.java rename to subprojects/infra/src/main/java/gg/essential/network/connectionmanager/relationship/FriendRequestState.java diff --git a/src/main/java/gg/essential/network/connectionmanager/relationship/RelationshipResponse.java b/subprojects/infra/src/main/java/gg/essential/network/connectionmanager/relationship/RelationshipResponse.java similarity index 78% rename from src/main/java/gg/essential/network/connectionmanager/relationship/RelationshipResponse.java rename to subprojects/infra/src/main/java/gg/essential/network/connectionmanager/relationship/RelationshipResponse.java index dcea42c..1806c6c 100644 --- a/src/main/java/gg/essential/network/connectionmanager/relationship/RelationshipResponse.java +++ b/subprojects/infra/src/main/java/gg/essential/network/connectionmanager/relationship/RelationshipResponse.java @@ -11,12 +11,9 @@ */ package gg.essential.network.connectionmanager.relationship; -import gg.essential.util.UUIDUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.UUID; - public class RelationshipResponse { @NotNull @@ -40,9 +37,6 @@ public RelationshipResponse(@NotNull FriendRequestState friendRequestState, @Nul public RelationshipResponse(@NotNull FriendRequestState friendRequestState, @Nullable RelationshipErrorResponse relationshipErrorResponse) { this.friendRequestState = friendRequestState; this.relationshipErrorResponse = relationshipErrorResponse; - if (relationshipErrorResponse != null) { - this.message = relationshipErrorResponse.getMessage(); - } } @NotNull @@ -56,16 +50,7 @@ public RelationshipErrorResponse getRelationshipErrorResponse() { } @Nullable - public String getMessage() { + public String getRawMessage() { return message; } - - public void displayToast(final UUID uuid) { - if (this.relationshipErrorResponse == null) { - return; - } - UUIDUtil.getName(uuid).thenAccept(username -> - this.relationshipErrorResponse.getShowToast().invoke(uuid, username) - ); - } } diff --git a/subprojects/infra/src/main/kotlin/gg/essential/network/CMConnection.kt b/subprojects/infra/src/main/kotlin/gg/essential/network/CMConnection.kt index d6dd0d0..7e30126 100644 --- a/subprojects/infra/src/main/kotlin/gg/essential/network/CMConnection.kt +++ b/subprojects/infra/src/main/kotlin/gg/essential/network/CMConnection.kt @@ -21,6 +21,10 @@ interface CMConnection { /** A main-thread supervisor [CoroutineScope] which has all its children cancelled on disconnect. */ val connectionScope: CoroutineScope + val isOpen: Boolean + + fun registerOnConnected(onConnected: () -> Unit) + fun registerPacketHandler(cls: Class, handler: (T) -> Unit) fun call(packet: Packet): Call = Call(this, packet) diff --git a/subprojects/infra/src/main/kotlin/gg/essential/network/Call.kt b/subprojects/infra/src/main/kotlin/gg/essential/network/Call.kt index f348794..c864736 100644 --- a/subprojects/infra/src/main/kotlin/gg/essential/network/Call.kt +++ b/subprojects/infra/src/main/kotlin/gg/essential/network/Call.kt @@ -12,6 +12,8 @@ package gg.essential.network import gg.essential.connectionmanager.common.packet.Packet +import gg.essential.util.ExponentialBackoff +import kotlinx.coroutines.delay import kotlinx.coroutines.suspendCancellableCoroutine import org.slf4j.LoggerFactory import java.util.concurrent.TimeUnit @@ -35,6 +37,10 @@ class Call( this.timeout = duration } + fun exponentialBackoff(start: Duration = 2.seconds, max: Duration = 60.seconds, factor: Double = 2.0): CallWithRetry { + return CallWithRetry(this, ExponentialBackoff(start, max, factor)) + } + fun fireAndForget() { @Suppress("DEPRECATION") connection.send(packet, null, TimeUnit.MILLISECONDS, timeout.inWholeMilliseconds) @@ -70,4 +76,23 @@ class Call( } } +class CallWithRetry( + private val base: Call, + private val backoff: ExponentialBackoff, +) { + suspend inline fun await(): T { + return awaitOneOf(T::class.java) as T + } + + suspend fun awaitOneOf(vararg classes: Class): Packet { + while (true) { + val response = base.awaitOneOf(*classes) + if (response != null) { + return response + } + delay(backoff.increment()) + } + } +} + class UnexpectedResponseException(val response: Packet) : Exception() diff --git a/subprojects/infra/src/main/kotlin/gg/essential/network/connectionmanager/relationship/RelationshipErrorResponse.kt b/subprojects/infra/src/main/kotlin/gg/essential/network/connectionmanager/relationship/RelationshipErrorResponse.kt new file mode 100644 index 0000000..23ecd6f --- /dev/null +++ b/subprojects/infra/src/main/kotlin/gg/essential/network/connectionmanager/relationship/RelationshipErrorResponse.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.network.connectionmanager.relationship + +enum class RelationshipErrorResponse { + + EXISTING_REQUEST_IS_PENDING, + SENDER_TYPE_VERIFIED_SLOT_LIMIT, + TARGET_TYPE_VERIFIED_SLOT_LIMIT, + NO_PENDING_REQUEST_TO_VERIFY, + VERIFIED_RELATIONSHIP_ALREADY_EXISTS, + NO_BLOCKED_RELATIONSHIP_TO_CONVERT, + SENDER_BLOCKED_TARGET, + TARGET_BLOCKED_SENDER, + SENDER_TYPE_OUTGOING_SLOT_LIMIT, + TARGET_TYPE_INCOMING_SLOT_LIMIT, + TARGET_PRIVACY_SETTING_FRIEND_OF_FRIENDS, + TARGET_PRIVACY_SETTING_NO_ONE, + TARGET_NOT_EXIST; + + companion object { + @JvmStatic + fun getResponse(response: String): RelationshipErrorResponse? { + return try { + valueOf(response) + } catch (e: IllegalArgumentException) { + null + } + } + } +} diff --git a/subprojects/lwjgl3/src/main/kotlin/gg/essential/util/lwjgl3/Lwjgl3Loader.kt b/subprojects/lwjgl3/src/main/kotlin/gg/essential/util/lwjgl3/Lwjgl3Loader.kt index 14fe3e0..dfd33cb 100644 --- a/subprojects/lwjgl3/src/main/kotlin/gg/essential/util/lwjgl3/Lwjgl3Loader.kt +++ b/subprojects/lwjgl3/src/main/kotlin/gg/essential/util/lwjgl3/Lwjgl3Loader.kt @@ -40,6 +40,9 @@ class Lwjgl3Loader(nativesDir: Path, gl3: Lazy) { addPackageExclusion("kotlinx.") // We'll also want to use netty in our API for buffer management. addPackageExclusion("io.netty.") + // Workaround for [Library.checkHash] taking the last hash it finds (but we return our jar first, so it actually + // ends up using the hash from MC's jar and when that don't match, it'll inappropriately emit an error message). + setResourceFilter { path -> path.endsWith(".sha1") } // Invoke the bootstrap code which sets up LWJGL's natives extraction code to look in a dedicated directory loadClass("$PKG_IMPL.Bootstrap") .getMethod("init", Path::class.java, Lazy::class.java) diff --git a/subprojects/utils/build.gradle.kts b/subprojects/utils/build.gradle.kts index 3fa56dd..c38a485 100644 --- a/subprojects/utils/build.gradle.kts +++ b/subprojects/utils/build.gradle.kts @@ -13,6 +13,7 @@ import gg.essential.gradle.util.KotlinVersion plugins { kotlin("multiplatform") + kotlin("plugin.serialization") } repositories { @@ -23,8 +24,11 @@ kotlin { jvm() sourceSets["commonMain"].dependencies { - implementation(kotlin("stdlib", KotlinVersion.minimal.stdlib)) - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${KotlinVersion.minimal.coroutines}") + val kotlin = KotlinVersion.minimal + implementation(kotlin("stdlib", kotlin.stdlib)) + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${kotlin.coroutines}") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:${kotlin.serialization}") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:${kotlin.serialization}") api("dev.folomeev.kotgl:kotgl-matrix:0.0.1-beta") } diff --git a/build-logic/settings.gradle.kts b/subprojects/vigilance2/build.gradle.kts similarity index 71% rename from build-logic/settings.gradle.kts rename to subprojects/vigilance2/build.gradle.kts index 5e6e207..b7055a1 100644 --- a/build-logic/settings.gradle.kts +++ b/subprojects/vigilance2/build.gradle.kts @@ -9,3 +9,16 @@ * commercialize, or otherwise exploit, or create derivative works based * upon, this file or any other in this repository, all of which is reserved by Essential. */ +import essential.universalLibs + +plugins { + kotlin("jvm") + id("gg.essential.defaults") +} + +kotlin.jvmToolchain(8) + +dependencies { + universalLibs() + implementation(project(":elementa:statev2")) +} diff --git a/src/main/kotlin/gg/essential/config/GuiBuilder.kt b/subprojects/vigilance2/src/main/kotlin/gg/essential/mod/vigilance2/GuiBuilder.kt similarity index 97% rename from src/main/kotlin/gg/essential/config/GuiBuilder.kt rename to subprojects/vigilance2/src/main/kotlin/gg/essential/mod/vigilance2/GuiBuilder.kt index 73dc5e2..5d7fec4 100644 --- a/src/main/kotlin/gg/essential/config/GuiBuilder.kt +++ b/subprojects/vigilance2/src/main/kotlin/gg/essential/mod/vigilance2/GuiBuilder.kt @@ -9,8 +9,9 @@ * commercialize, or otherwise exploit, or create derivative works based * upon, this file or any other in this repository, all of which is reserved by Essential. */ -package gg.essential.config +package gg.essential.mod.vigilance2 +import gg.essential.gui.elementa.state.v2.ListState import gg.essential.gui.elementa.state.v2.MutableState import gg.essential.gui.elementa.state.v2.Observer import gg.essential.gui.elementa.state.v2.State @@ -18,8 +19,8 @@ import gg.essential.gui.elementa.state.v2.combinators.bimap import gg.essential.gui.elementa.state.v2.mutableStateOf import gg.essential.gui.elementa.state.v2.stateOf import gg.essential.gui.elementa.state.v2.toListState -import gg.essential.gui.vigilancev2.builder.StateBackedPropertyValue -import gg.essential.gui.vigilancev2.builder.VisibleDependencyPredicate +import gg.essential.mod.vigilance2.builder.StateBackedPropertyValue +import gg.essential.mod.vigilance2.builder.VisibleDependencyPredicate import gg.essential.vigilance.Vigilant import gg.essential.vigilance.data.KFunctionBackedPropertyValue import gg.essential.vigilance.data.PropertyAttributesExt @@ -36,7 +37,7 @@ class GuiBuilder internal constructor( private val categories = mutableListOf() companion object { - fun build(title: String, block: GuiBuilder.() -> Unit): GuiFactory { + fun build(title: String, block: GuiBuilder.() -> Unit): ListState { val guiInstance = object : Vigilant(File.createTempFile("dummy-config", ".toml"), title, PassivePropertyCollector()) {} val builder = GuiBuilder(guiInstance).apply(block) @@ -50,7 +51,7 @@ class GuiBuilder internal constructor( } }.toListState() - return GuiFactory(properties) + return properties } } diff --git a/src/main/kotlin/gg/essential/config/PassivePropertyCollector.kt b/subprojects/vigilance2/src/main/kotlin/gg/essential/mod/vigilance2/PassivePropertyCollector.kt similarity index 95% rename from src/main/kotlin/gg/essential/config/PassivePropertyCollector.kt rename to subprojects/vigilance2/src/main/kotlin/gg/essential/mod/vigilance2/PassivePropertyCollector.kt index 663054c..ed57a0d 100644 --- a/src/main/kotlin/gg/essential/config/PassivePropertyCollector.kt +++ b/subprojects/vigilance2/src/main/kotlin/gg/essential/mod/vigilance2/PassivePropertyCollector.kt @@ -9,7 +9,7 @@ * commercialize, or otherwise exploit, or create derivative works based * upon, this file or any other in this repository, all of which is reserved by Essential. */ -package gg.essential.config +package gg.essential.mod.vigilance2 import gg.essential.vigilance.Vigilant import gg.essential.vigilance.data.PropertyCollector diff --git a/src/main/kotlin/gg/essential/config/Vigilant2.kt b/subprojects/vigilance2/src/main/kotlin/gg/essential/mod/vigilance2/Vigilant2.kt similarity index 94% rename from src/main/kotlin/gg/essential/config/Vigilant2.kt rename to subprojects/vigilance2/src/main/kotlin/gg/essential/mod/vigilance2/Vigilant2.kt index 4fb103f..c4ebf79 100644 --- a/src/main/kotlin/gg/essential/config/Vigilant2.kt +++ b/subprojects/vigilance2/src/main/kotlin/gg/essential/mod/vigilance2/Vigilant2.kt @@ -9,13 +9,14 @@ * commercialize, or otherwise exploit, or create derivative works based * upon, this file or any other in this repository, all of which is reserved by Essential. */ -package gg.essential.config +package gg.essential.mod.vigilance2 +import gg.essential.gui.elementa.state.v2.ListState import gg.essential.gui.elementa.state.v2.MutableState import gg.essential.gui.elementa.state.v2.ReferenceHolderImpl import gg.essential.gui.elementa.state.v2.combinators.bimap import gg.essential.gui.elementa.state.v2.mutableStateOf -import gg.essential.gui.vigilancev2.builder.StateBackedPropertyValue +import gg.essential.mod.vigilance2.builder.StateBackedPropertyValue import gg.essential.vigilance.Vigilant import gg.essential.vigilance.data.Migration import gg.essential.vigilance.data.PropertyAttributesExt @@ -72,10 +73,10 @@ open class Vigilant2 { //endregion - protected fun buildGui(title: String, block: GuiBuilder.() -> Unit): GuiFactory = + protected fun buildGui(title: String, block: GuiBuilder.() -> Unit): ListState = GuiBuilder.build(title, block) - protected fun lazyBuildGui(title: String, block: GuiBuilder.() -> Unit): Lazy = + protected fun lazyBuildGui(title: String, block: GuiBuilder.() -> Unit): Lazy> = lazy { buildGui(title, block) } open val migrations: List = emptyList() diff --git a/gui/vigilance/src/main/kotlin/gg/essential/gui/vigilancev2/builder/StateBackedPropertyValue.kt b/subprojects/vigilance2/src/main/kotlin/gg/essential/mod/vigilance2/builder/StateBackedPropertyValue.kt similarity index 91% rename from gui/vigilance/src/main/kotlin/gg/essential/gui/vigilancev2/builder/StateBackedPropertyValue.kt rename to subprojects/vigilance2/src/main/kotlin/gg/essential/mod/vigilance2/builder/StateBackedPropertyValue.kt index 3e64603..9c13182 100644 --- a/gui/vigilance/src/main/kotlin/gg/essential/gui/vigilancev2/builder/StateBackedPropertyValue.kt +++ b/subprojects/vigilance2/src/main/kotlin/gg/essential/mod/vigilance2/builder/StateBackedPropertyValue.kt @@ -9,14 +9,14 @@ * commercialize, or otherwise exploit, or create derivative works based * upon, this file or any other in this repository, all of which is reserved by Essential. */ -package gg.essential.gui.vigilancev2.builder +package gg.essential.mod.vigilance2.builder import gg.essential.gui.elementa.state.v2.MutableState import gg.essential.vigilance.Vigilant import gg.essential.vigilance.data.PropertyValue class StateBackedPropertyValue( - internal val state: MutableState, + val state: MutableState, override val writeDataToFile: Boolean, ) : PropertyValue() { override fun getValue(instance: Vigilant): Any? = state.get() diff --git a/gui/vigilance/src/main/kotlin/gg/essential/gui/vigilancev2/builder/VisibleDependencyPredicate.kt b/subprojects/vigilance2/src/main/kotlin/gg/essential/mod/vigilance2/builder/VisibleDependencyPredicate.kt similarity index 95% rename from gui/vigilance/src/main/kotlin/gg/essential/gui/vigilancev2/builder/VisibleDependencyPredicate.kt rename to subprojects/vigilance2/src/main/kotlin/gg/essential/mod/vigilance2/builder/VisibleDependencyPredicate.kt index c8cf524..c8f9bda 100644 --- a/gui/vigilance/src/main/kotlin/gg/essential/gui/vigilancev2/builder/VisibleDependencyPredicate.kt +++ b/subprojects/vigilance2/src/main/kotlin/gg/essential/mod/vigilance2/builder/VisibleDependencyPredicate.kt @@ -9,7 +9,7 @@ * commercialize, or otherwise exploit, or create derivative works based * upon, this file or any other in this repository, all of which is reserved by Essential. */ -package gg.essential.gui.vigilancev2.builder +package gg.essential.mod.vigilance2.builder import gg.essential.gui.elementa.state.v2.State diff --git a/versions/1.12.2-1.8.9.txt b/versions/1.12.2-1.8.9.txt index bafa122..54657b6 100644 --- a/versions/1.12.2-1.8.9.txt +++ b/versions/1.12.2-1.8.9.txt @@ -1,3 +1,4 @@ +net.minecraft.util.text.TextFormatting net.minecraft.util.EnumChatFormatting net.minecraft.client.renderer.ItemRenderer renderArm() renderPlayerArm() net.minecraft.client.renderer.ItemRenderer renderArmFirstPerson() renderPlayerArms() net.minecraft.entity.Entity getDataManager() getDataWatcher() diff --git a/versions/1.16.2-1.12.2.txt b/versions/1.16.2-1.12.2.txt index 0cfea40..b4df993 100644 --- a/versions/1.16.2-1.12.2.txt +++ b/versions/1.16.2-1.12.2.txt @@ -29,6 +29,8 @@ net.minecraft.client.renderer.entity.EntityRenderer net.minecraft.client.rendere net.minecraft.client.renderer.entity.EntityRenderer renderName() net.minecraft.client.renderer.entity.Render renderLivingLabel() net.minecraft.client.renderer.entity.LivingRenderer getEntityModel() net.minecraft.client.renderer.entity.RenderPlayer getMainModel() net.minecraft.client.renderer.entity.PlayerRenderer net.minecraft.client.renderer.entity.RenderPlayer +net.minecraft.client.renderer.entity.layers.BipedArmorLayer net.minecraft.client.renderer.entity.layers.LayerArmorBase +net.minecraft.client.renderer.entity.layers.BipedArmorLayer func_241739_a_() renderArmorLayer() net.minecraft.client.renderer.entity.layers.CapeLayer net.minecraft.client.renderer.entity.layers.LayerCape net.minecraft.client.renderer.entity.layers.HeadLayer net.minecraft.client.renderer.entity.layers.LayerCustomHead net.minecraft.client.renderer.entity.layers.ParrotVariantLayer net.minecraft.client.renderer.entity.layers.LayerEntityOnShoulder @@ -36,6 +38,7 @@ net.minecraft.client.renderer.texture.Texture net.minecraft.client.renderer.text net.minecraft.client.resources.LegacyResourcePackWrapper net.minecraft.client.resources.LegacyV2Adapter net.minecraft.client.resources.LegacyResourcePackWrapper locationMap net.minecraft.client.resources.LegacyV2Adapter pack net.minecraft.client.resources.SkinManager$ISkinAvailableCallback net.minecraft.client.resources.SkinManager$SkinAvailableCallback +net.minecraft.item.UseAction net.minecraft.item.EnumAction net.minecraft.nbt.CompoundNBT net.minecraft.nbt.NBTTagCompound net.minecraft.nbt.CompoundNBT put() setTag() net.minecraft.nbt.ListNBT net.minecraft.nbt.NBTTagList diff --git a/versions/1.16.2-fabric/src/main/kotlin/gg/essential/compatibility/ModMenuCompat.kt b/versions/1.16.2-fabric/src/main/kotlin/gg/essential/compatibility/ModMenuCompat.kt index 5300fb0..24a14b8 100644 --- a/versions/1.16.2-fabric/src/main/kotlin/gg/essential/compatibility/ModMenuCompat.kt +++ b/versions/1.16.2-fabric/src/main/kotlin/gg/essential/compatibility/ModMenuCompat.kt @@ -14,11 +14,11 @@ package gg.essential.compatibility import com.terraformersmc.modmenu.api.ConfigScreenFactory import com.terraformersmc.modmenu.api.ModMenuApi -import gg.essential.config.EssentialConfig +import gg.essential.config.McEssentialConfig class ModMenuCompat : ModMenuApi { override fun getModConfigScreenFactory() = ConfigScreenFactory { - EssentialConfig.gui() + McEssentialConfig.gui() } } //#endif diff --git a/versions/1.16.2-forge/src/main/java/gg/essential/mixins/transformers/client/gui/Mixin_PreventMovingOfServersInCustomTabs.java b/versions/1.16.2-forge/src/main/java/gg/essential/mixins/transformers/client/gui/Mixin_PreventMovingOfServersInCustomTabs.java index ca70d18..90b62b1 100644 --- a/versions/1.16.2-forge/src/main/java/gg/essential/mixins/transformers/client/gui/Mixin_PreventMovingOfServersInCustomTabs.java +++ b/versions/1.16.2-forge/src/main/java/gg/essential/mixins/transformers/client/gui/Mixin_PreventMovingOfServersInCustomTabs.java @@ -23,6 +23,11 @@ import org.spongepowered.asm.mixin.injection.ModifyArg; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +//#if MC>=12102 +//$$ import net.minecraft.client.render.RenderLayer; +//$$ import java.util.function.Function; +//#endif + //#if MC>=12000 //$$ import net.minecraft.util.Identifier; //#endif @@ -37,8 +42,13 @@ public abstract class Mixin_PreventMovingOfServersInCustomTabs { private MultiplayerScreen owner; //#if MC>=12002 + //#if MC>=12102 + //$$ @ModifyArg(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/DrawContext;drawGuiTexture(Ljava/util/function/Function;Lnet/minecraft/util/Identifier;IIII)V"), index = 2) + //$$ private int hideMovingButtonsInCustomTabs(Function renderLayers, Identifier location, int x, int y, int width, int height) { + //#else //$$ @ModifyArg(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/DrawContext;drawGuiTexture(Lnet/minecraft/util/Identifier;IIII)V"), index = 1) //$$ private int hideMovingButtonsInCustomTabs(Identifier location, int x, int y, int width, int height) { + //#endif //$$ if (EssentialConfig.INSTANCE.getCurrentMultiplayerTab() != 0 && location.getPath().startsWith("server_list/move_")) { //$$ x = Integer.MIN_VALUE; //$$ } diff --git a/versions/1.16.2-forge/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_DisableArmorRendering.java b/versions/1.16.2-forge/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_DisableArmorRendering.java deleted file mode 100644 index 023e812..0000000 --- a/versions/1.16.2-forge/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_DisableArmorRendering.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.mixins.transformers.client.renderer.entity; - -import com.mojang.blaze3d.matrix.MatrixStack; -import gg.essential.config.EssentialConfig; -import gg.essential.cosmetics.EssentialModelRenderer; -import gg.essential.mixins.impl.client.entity.AbstractClientPlayerExt; -import net.minecraft.client.renderer.IRenderTypeBuffer; -import net.minecraft.client.renderer.entity.layers.BipedArmorLayer; -import net.minecraft.client.renderer.entity.model.BipedModel; -import net.minecraft.entity.LivingEntity; -import net.minecraft.inventory.EquipmentSlotType; -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(value = BipedArmorLayer.class) -public abstract class Mixin_DisableArmorRendering> { - - @Inject(method = "func_241739_a_", at = @At(value = "HEAD"), cancellable = true) - private void essential$disableArmorRendering(MatrixStack arg, IRenderTypeBuffer arg2, T arg3, EquipmentSlotType arg4, int j, A arg5, CallbackInfo info) { - if (arg3 instanceof AbstractClientPlayerExt) { - if (arg4.getSlotType() == EquipmentSlotType.Group.ARMOR) { - final AbstractClientPlayerExt playerExt = (AbstractClientPlayerExt) arg3; - int armorHidingSetting = EssentialConfig.INSTANCE.getCosmeticArmorSetting(arg3); - if (armorHidingSetting == 3 || (armorHidingSetting == 1 && playerExt.getCosmeticsState().getPartsEquipped().contains(arg4.getIndex()) && !EssentialModelRenderer.suppressCosmeticRendering)) { - info.cancel(); - } - } - } - } -} diff --git a/versions/1.16.2-forge/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_DisableElytraRendering.java b/versions/1.16.2-forge/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_DisableElytraRendering.java deleted file mode 100644 index b0bcd2e..0000000 --- a/versions/1.16.2-forge/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_DisableElytraRendering.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2024 ModCore Inc. All rights reserved. - * - * This code is part of ModCore Inc.'s Essential Mod repository and is protected - * under copyright registration # TX0009138511. For the full license, see: - * https://github.com/EssentialGG/Essential/blob/main/LICENSE - * - * You may not use, copy, reproduce, modify, sell, license, distribute, - * commercialize, or otherwise exploit, or create derivative works based - * upon, this file or any other in this repository, all of which is reserved by Essential. - */ -package gg.essential.mixins.transformers.client.renderer.entity; - -import com.mojang.blaze3d.matrix.MatrixStack; -import gg.essential.mixins.impl.client.renderer.entity.ArmorRenderingUtil; -import net.minecraft.client.renderer.IRenderTypeBuffer; -import net.minecraft.client.renderer.entity.layers.ElytraLayer; -import net.minecraft.entity.LivingEntity; -import net.minecraft.inventory.EquipmentSlotType; -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(value = ElytraLayer.class) -public class Mixin_DisableElytraRendering { - - @Inject(method = "render", at = @At(value = "HEAD"), cancellable = true) - private void essential$disableElytraRendering(MatrixStack matrixStackIn, IRenderTypeBuffer bufferIn, int packedLightIn, T entitylivingbaseIn, float limbSwing, float limbSwingAmount, float partialTicks, float ageInTicks, float netHeadYaw, float headPitch, CallbackInfo info) { - if (ArmorRenderingUtil.shouldDisableArmor(entitylivingbaseIn, EquipmentSlotType.CHEST.getIndex())) { - info.cancel(); - } - } -} diff --git a/versions/1.16.2-forge/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_RenderNameplateIcon.java b/versions/1.16.2-forge/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_RenderNameplateIcon.java index 50b66f7..4695291 100644 --- a/versions/1.16.2-forge/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_RenderNameplateIcon.java +++ b/versions/1.16.2-forge/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_RenderNameplateIcon.java @@ -25,24 +25,49 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +//#if MC>=12102 +//$$ import gg.essential.mixins.impl.client.model.PlayerEntityRenderStateExt; +//$$ import net.minecraft.client.render.entity.state.EntityRenderState; +//#endif + @Mixin(EntityRenderer.class) public class Mixin_RenderNameplateIcon { @Inject(method = "renderName", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/matrix/MatrixStack;scale(FFF)V", shift = At.Shift.AFTER, ordinal = 0)) - private void essential$translateNameplate(CallbackInfo ci, @Local(argsOnly = true) T entity, @Local(argsOnly = true) MatrixStack matrixStack) { + private void essential$translateNameplate( + CallbackInfo ci, + //#if MC>=12102 + //$$ @Local(argsOnly = true) EntityRenderState state, + //#else + @Local(argsOnly = true) T entity, + //#endif + @Local(argsOnly = true) MatrixStack matrixStack + ) { + //#if MC>=12102 + //$$ if (!(state instanceof PlayerEntityRenderStateExt)) return; + //$$ Entity entity = ((PlayerEntityRenderStateExt) state).essential$getEntity(); + //#endif } @Inject(method = "renderName", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/matrix/MatrixStack;pop()V")) private void renderEssentialIndicator( + //#if MC>=12102 + //$$ EntityRenderState state, + //#else T entity, + //#endif ITextComponent name, MatrixStack vMatrixStack, IRenderTypeBuffer bufferIn, int packedLightIn, - //#if MC>=12005 + //#if MC>=12005 && MC<12102 //$$ float timeDelta, //#endif CallbackInfo ci ) { + //#if MC>=12102 + //$$ if (!(state instanceof PlayerEntityRenderStateExt)) return; + //$$ Entity entity = ((PlayerEntityRenderStateExt) state).essential$getEntity(); + //#endif if (OnlineIndicator.currentlyDrawingEntityName()) { OnlineIndicator.drawNametagIndicator(new UMatrixStack(vMatrixStack), bufferIn, entity, new UTextComponent(name.deepCopy()).getFormattedText(), packedLightIn); } diff --git a/versions/1.16.2-forge/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_StoreArmorRenderedState.java b/versions/1.16.2-forge/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_StoreArmorRenderedState.java index f73092c..7d4ac89 100644 --- a/versions/1.16.2-forge/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_StoreArmorRenderedState.java +++ b/versions/1.16.2-forge/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_StoreArmorRenderedState.java @@ -11,11 +11,9 @@ */ package gg.essential.mixins.transformers.client.renderer.entity; -import com.mojang.blaze3d.matrix.MatrixStack; +import com.llamalad7.mixinextras.sugar.Local; import gg.essential.mixins.impl.client.entity.AbstractClientPlayerExt; -import net.minecraft.client.renderer.IRenderTypeBuffer; import net.minecraft.client.renderer.entity.layers.BipedArmorLayer; -import net.minecraft.client.renderer.entity.model.BipedModel; import net.minecraft.entity.LivingEntity; import net.minecraft.inventory.EquipmentSlotType; import org.spongepowered.asm.mixin.Mixin; @@ -23,21 +21,54 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +//#if MC>=12102 +//$$ import gg.essential.mixins.impl.client.model.PlayerEntityRenderStateExt; +//$$ import net.minecraft.client.render.entity.state.BipedEntityRenderState; +//#endif + @Mixin(BipedArmorLayer.class) -public abstract class Mixin_StoreArmorRenderedState> { +public abstract class Mixin_StoreArmorRenderedState { + + private static final ThreadLocal playerThreadLocal = new ThreadLocal<>(); + + //#if MC>=12102 + //$$ private static final String RENDER = "render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/client/render/entity/state/BipedEntityRenderState;FF)V"; + //#else + private static final String RENDER = "render(Lcom/mojang/blaze3d/matrix/MatrixStack;Lnet/minecraft/client/renderer/IRenderTypeBuffer;ILnet/minecraft/entity/LivingEntity;FFFFFF)V"; + //#endif - @Inject(method = "render(Lcom/mojang/blaze3d/matrix/MatrixStack;Lnet/minecraft/client/renderer/IRenderTypeBuffer;ILnet/minecraft/entity/LivingEntity;FFFFFF)V", at = @At("HEAD")) - private void essential$assumeArmorRenderingSuppressed(MatrixStack matrixStackIn, IRenderTypeBuffer bufferIn, int packedLightIn, T entitylivingbaseIn, float limbSwing, float limbSwingAmount, float partialTicks, float ageInTicks, float netHeadYaw, float headPitch, CallbackInfo info) { + @Inject(method = RENDER, at = @At("HEAD")) + private void essential$assumeArmorRenderingSuppressed( + CallbackInfo ci, + //#if MC>=12102 + //$$ @Local(argsOnly = true) BipedEntityRenderState state + //#else + @Local(argsOnly = true) LivingEntity entitylivingbaseIn + //#endif + ) { + //#if MC>=12102 + //$$ if (!(state instanceof PlayerEntityRenderStateExt)) return; + //$$ LivingEntity entitylivingbaseIn = ((PlayerEntityRenderStateExt) state).essential$getEntity(); + //#endif if (entitylivingbaseIn instanceof AbstractClientPlayerExt) { final AbstractClientPlayerExt playerExt = (AbstractClientPlayerExt) entitylivingbaseIn; + playerThreadLocal.set(playerExt); playerExt.assumeArmorRenderingSuppressed(); } } + @Inject(method = RENDER, at = @At("RETURN")) + private void resetThreadLocal(CallbackInfo ci) { + playerThreadLocal.remove(); + } + @Inject(method = "func_241739_a_", at = @At(value = "HEAD", shift = At.Shift.AFTER)) - private void essential$markRenderingNotSuppressed(MatrixStack arg, IRenderTypeBuffer arg2, T arg3, EquipmentSlotType arg4, int j, A arg5, CallbackInfo info) { - if (arg3 instanceof AbstractClientPlayerExt) { - final AbstractClientPlayerExt playerExt = (AbstractClientPlayerExt) arg3; + private void essential$markRenderingNotSuppressed( + CallbackInfo ci, + @Local(argsOnly = true) EquipmentSlotType arg4 + ) { + AbstractClientPlayerExt playerExt = playerThreadLocal.get(); + if (playerExt != null) { if (arg4.getSlotType() == EquipmentSlotType.Group.ARMOR) { playerExt.armorRenderingNotSuppressed(arg4.getIndex()); } diff --git a/versions/1.16.2-forge/src/main/java/gg/essential/mixins/transformers/compatibility/vanilla/Mixin_FixKeyboardNavigation.java b/versions/1.16.2-forge/src/main/java/gg/essential/mixins/transformers/compatibility/vanilla/Mixin_FixKeyboardNavigation.java new file mode 100644 index 0000000..bed5467 --- /dev/null +++ b/versions/1.16.2-forge/src/main/java/gg/essential/mixins/transformers/compatibility/vanilla/Mixin_FixKeyboardNavigation.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.mixins.transformers.compatibility.vanilla; + +import gg.essential.mixins.DummyTarget; +import org.spongepowered.asm.mixin.Mixin; + +// 1.12.2 and below +@Mixin(DummyTarget.class) +public class Mixin_FixKeyboardNavigation { +} diff --git a/versions/1.16.2-forge/src/main/java/gg/essential/mixins/transformers/events/Mixin_GuiClickEvent_Priority.java b/versions/1.16.2-forge/src/main/java/gg/essential/mixins/transformers/events/Mixin_GuiClickEvent_Priority.java index 9946211..b1f8758 100644 --- a/versions/1.16.2-forge/src/main/java/gg/essential/mixins/transformers/events/Mixin_GuiClickEvent_Priority.java +++ b/versions/1.16.2-forge/src/main/java/gg/essential/mixins/transformers/events/Mixin_GuiClickEvent_Priority.java @@ -9,7 +9,7 @@ * commercialize, or otherwise exploit, or create derivative works based * upon, this file or any other in this repository, all of which is reserved by Essential. */ -// 1.16 and above +// 1.16 - 1.21 package gg.essential.mixins.transformers.events; import gg.essential.Essential; diff --git a/versions/1.16.2-forge/src/main/java/gg/essential/mixins/transformers/events/Mixin_GuiDrawScreenEvent_Priority_LoadingScreen.java b/versions/1.16.2-forge/src/main/java/gg/essential/mixins/transformers/events/Mixin_GuiDrawScreenEvent_Priority_LoadingScreen.java new file mode 100644 index 0000000..9ece4b3 --- /dev/null +++ b/versions/1.16.2-forge/src/main/java/gg/essential/mixins/transformers/events/Mixin_GuiDrawScreenEvent_Priority_LoadingScreen.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.mixins.transformers.events; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.mojang.blaze3d.matrix.MatrixStack; +import gg.essential.Essential; +import gg.essential.event.gui.GuiDrawScreenEvent; +import gg.essential.universal.UMatrixStack; +import net.minecraft.client.gui.ResourceLoadProgressGui; +import net.minecraft.client.gui.screen.Screen; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +//#if MC>=12000 +//$$ import net.minecraft.client.gui.DrawContext; +//#endif + +@Mixin(ResourceLoadProgressGui.class) +public abstract class Mixin_GuiDrawScreenEvent_Priority_LoadingScreen { + //#if MC>=12000 + //$$ private static final String SCREEN_RENDER = "Lnet/minecraft/client/gui/screen/Screen;render(Lnet/minecraft/client/gui/DrawContext;IIF)V"; + //#else + private static final String SCREEN_RENDER = "Lnet/minecraft/client/gui/screen/Screen;render(Lcom/mojang/blaze3d/matrix/MatrixStack;IIF)V"; + //#endif + + @WrapOperation(method = "render", at = @At(value = "INVOKE", target = SCREEN_RENDER)) + private void wrapDrawScreen( + Screen screen, + //#if MC>=12000 + //$$ DrawContext context, + //#else + MatrixStack vMatrixStack, + //#endif + int mouseX, + int mouseY, + float partialTicks, + Operation original + ) { + //#if MC>=12000 + //$$ UMatrixStack matrixStack = new UMatrixStack(context.getMatrices()); + //#else + UMatrixStack matrixStack = new UMatrixStack(vMatrixStack); + //#endif + + GuiDrawScreenEvent.Priority preEvent = new GuiDrawScreenEvent.Priority(screen, matrixStack, mouseX, mouseY, partialTicks, false); + Essential.EVENT_BUS.post(preEvent); + + if (preEvent.getMouseX() != preEvent.getOriginalMouseX()) { + mouseX = preEvent.getMouseX(); + } + if (preEvent.getMouseY() != preEvent.getOriginalMouseY()) { + mouseY = preEvent.getMouseY(); + } + + original.call( + screen, + //#if MC>=12000 + //$$ context, + //#else + vMatrixStack, + //#endif + mouseX, + mouseY, + partialTicks + ); + + Essential.EVENT_BUS.post(new GuiDrawScreenEvent.Priority(screen, matrixStack, mouseX, mouseY, partialTicks, true)); + } +} diff --git a/versions/1.16.2-forge/src/main/java/gg/essential/mixins/transformers/feature/cosmetics/Mixin_CutoutCapeTextures.java b/versions/1.16.2-forge/src/main/java/gg/essential/mixins/transformers/feature/cosmetics/Mixin_CutoutCapeTextures.java index 0ece99a..092028c 100644 --- a/versions/1.16.2-forge/src/main/java/gg/essential/mixins/transformers/feature/cosmetics/Mixin_CutoutCapeTextures.java +++ b/versions/1.16.2-forge/src/main/java/gg/essential/mixins/transformers/feature/cosmetics/Mixin_CutoutCapeTextures.java @@ -25,7 +25,13 @@ */ @Mixin(CapeLayer.class) public abstract class Mixin_CutoutCapeTextures { - @WrapOperation(method = "render(Lcom/mojang/blaze3d/matrix/MatrixStack;Lnet/minecraft/client/renderer/IRenderTypeBuffer;ILnet/minecraft/client/entity/player/AbstractClientPlayerEntity;FFFFFF)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/RenderType;getEntitySolid(Lnet/minecraft/util/ResourceLocation;)Lnet/minecraft/client/renderer/RenderType;")) + //#if MC>=12102 + //$$ private static final String RENDER = "render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/client/render/entity/state/PlayerEntityRenderState;FF)V"; + //#else + private static final String RENDER = "render(Lcom/mojang/blaze3d/matrix/MatrixStack;Lnet/minecraft/client/renderer/IRenderTypeBuffer;ILnet/minecraft/client/entity/player/AbstractClientPlayerEntity;FFFFFF)V"; + //#endif + + @WrapOperation(method = RENDER, at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/RenderType;getEntitySolid(Lnet/minecraft/util/ResourceLocation;)Lnet/minecraft/client/renderer/RenderType;")) private RenderType essential$useCutoutRenderType(ResourceLocation resourceLocation, Operation operation) { RenderType vanillaRenderType = RenderType.getEntitySolid(resourceLocation); RenderType actualRenderType = operation.call(resourceLocation); diff --git a/versions/1.16.2-forge/src/main/java/gg/essential/mixins/transformers/server/Mixin_ControlAreCommandsAllowed.java b/versions/1.16.2-forge/src/main/java/gg/essential/mixins/transformers/server/Mixin_ControlAreCommandsAllowed.java index 1a9bdbb..14184aa 100644 --- a/versions/1.16.2-forge/src/main/java/gg/essential/mixins/transformers/server/Mixin_ControlAreCommandsAllowed.java +++ b/versions/1.16.2-forge/src/main/java/gg/essential/mixins/transformers/server/Mixin_ControlAreCommandsAllowed.java @@ -13,6 +13,7 @@ import gg.essential.Essential; import gg.essential.network.connectionmanager.sps.SPSManager; +import gg.essential.sps.McIntegratedServerManager; import net.minecraft.world.WorldSettings; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; @@ -24,6 +25,15 @@ public class Mixin_ControlAreCommandsAllowed { @Inject(method = "isCommandsAllowed", at = @At("HEAD"), cancellable = true) private void isCommandsAllowed(CallbackInfoReturnable cir) { + McIntegratedServerManager manager = Essential.getInstance().getIntegratedServerManager().getUntracked(); + if (manager != null) { + Boolean cheatsEnabled = manager.getAppliedCheatsEnabled(); + if (cheatsEnabled != null) { + cir.setReturnValue(cheatsEnabled); + return; + } + } + final SPSManager spsManager = Essential.getInstance().getConnectionManager().getSpsManager(); if (spsManager.getLocalSession() == null) { return; diff --git a/versions/1.19-fabric/src/main/java/gg/essential/mixins/transformers/feature/sps/Mixin_IntegratedServerResourcePack.java b/versions/1.19-fabric/src/main/java/gg/essential/mixins/transformers/feature/sps/Mixin_IntegratedServerResourcePack.java index 56f5b39..850deb8 100644 --- a/versions/1.19-fabric/src/main/java/gg/essential/mixins/transformers/feature/sps/Mixin_IntegratedServerResourcePack.java +++ b/versions/1.19-fabric/src/main/java/gg/essential/mixins/transformers/feature/sps/Mixin_IntegratedServerResourcePack.java @@ -12,7 +12,10 @@ package gg.essential.mixins.transformers.feature.sps; import gg.essential.Essential; +import gg.essential.mixins.ext.server.integrated.IntegratedServerExt; import gg.essential.network.connectionmanager.sps.SPSManager; +import gg.essential.sps.IntegratedServerManager; +import gg.essential.sps.McIntegratedServerManager; import net.minecraft.server.MinecraftServer; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; @@ -31,6 +34,23 @@ public class Mixin_IntegratedServerResourcePack { @Inject(method = "getResourcePackProperties", at = @At("HEAD"), cancellable = true) private void getResourcePackProperties(CallbackInfoReturnable> info) { + if (this instanceof IntegratedServerExt) { + McIntegratedServerManager manager = ((IntegratedServerExt) this).getEssential$manager(); + Optional resourcePack = manager.getAppliedServerResourcePack(); + if (resourcePack != null && resourcePack.isPresent()) { + String url = resourcePack.get().getUrl(); + String checksum = resourcePack.get().getChecksum(); + info.setReturnValue(Optional.of(new MinecraftServer.ServerResourcePackProperties( + //#if MC>=12004 + //$$ // Server resource packs are now given a unique ID for caching purposes. + //$$ // Vanilla uses this fallback when the ID is not present. + //$$ UUID.nameUUIDFromBytes(url.getBytes(StandardCharsets.UTF_8)), + //#endif + url, checksum, false, null))); + return; + } + } + final SPSManager spsManager = Essential.getInstance().getConnectionManager().getSpsManager(); final String resourcePackUrl = spsManager.getResourcePackUrl(); final String resourcePackChecksum = spsManager.getResourcePackChecksum(); diff --git a/versions/1.19.4-fabric/src/main/java/gg/essential/mixins/transformers/events/Mixin_RenderTickEvent.java b/versions/1.19.4-fabric/src/main/java/gg/essential/mixins/transformers/events/Mixin_RenderTickEvent.java index 6a8dd13..075d0c0 100644 --- a/versions/1.19.4-fabric/src/main/java/gg/essential/mixins/transformers/events/Mixin_RenderTickEvent.java +++ b/versions/1.19.4-fabric/src/main/java/gg/essential/mixins/transformers/events/Mixin_RenderTickEvent.java @@ -65,7 +65,7 @@ private void fireTickEvent( float partialTicksMenu = UMinecraft.getMinecraft().getTickDelta(); float partialTicksInGame = tickDelta; //#endif - Essential.EVENT_BUS.post(new RenderTickEvent(pre, matrixStack, partialTicksMenu, partialTicksInGame)); + Essential.EVENT_BUS.post(new RenderTickEvent(pre, false, matrixStack, partialTicksMenu, partialTicksInGame)); } } diff --git a/versions/1.20-fabric/src/main/java/gg/essential/mixins/transformers/feature/emote/Mixin_AllowMovementDuringEmoteWheel_HandleKeybinds.java b/versions/1.20-fabric/src/main/java/gg/essential/mixins/transformers/feature/emote/Mixin_AllowMovementDuringEmoteWheel_HandleKeybinds.java index 45234ad..9b5a2b2 100644 --- a/versions/1.20-fabric/src/main/java/gg/essential/mixins/transformers/feature/emote/Mixin_AllowMovementDuringEmoteWheel_HandleKeybinds.java +++ b/versions/1.20-fabric/src/main/java/gg/essential/mixins/transformers/feature/emote/Mixin_AllowMovementDuringEmoteWheel_HandleKeybinds.java @@ -24,7 +24,11 @@ public abstract class Mixin_AllowMovementDuringEmoteWheel_HandleKeybinds { @ModifyExpressionValue( method = "tick", at = @At(value = "FIELD", target = "Lnet/minecraft/client/MinecraftClient;currentScreen:Lnet/minecraft/client/gui/screen/Screen;", ordinal = 0), + //#if MC>=12102 + //$$ slice = @Slice(from = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/hud/DebugHud;shouldShowDebugHud()Z")) + //#else slice = @Slice(from = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/Screen;wrapScreenError(Ljava/lang/Runnable;Ljava/lang/String;Ljava/lang/String;)V")) + //#endif ) private Screen essential$emoteWheelAllowsMovement(Screen screen) { if (screen instanceof EmoteWheel) { diff --git a/versions/1.20-fabric/src/main/java/gg/essential/mixins/transformers/feature/emote/Mixin_AllowMovementDuringEmoteWheel_HandleKeys.java b/versions/1.20-fabric/src/main/java/gg/essential/mixins/transformers/feature/emote/Mixin_AllowMovementDuringEmoteWheel_HandleKeys.java index 5bf9c01..5c1cd11 100644 --- a/versions/1.20-fabric/src/main/java/gg/essential/mixins/transformers/feature/emote/Mixin_AllowMovementDuringEmoteWheel_HandleKeys.java +++ b/versions/1.20-fabric/src/main/java/gg/essential/mixins/transformers/feature/emote/Mixin_AllowMovementDuringEmoteWheel_HandleKeys.java @@ -24,7 +24,11 @@ public abstract class Mixin_AllowMovementDuringEmoteWheel_HandleKeys { @ModifyExpressionValue( method = "onKey", at = @At(value = "FIELD", target = "Lnet/minecraft/client/MinecraftClient;currentScreen:Lnet/minecraft/client/gui/screen/Screen;", ordinal = 0), + //#if MC>=12102 + //$$ slice = @Slice(from = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/Screen;keyPressed(III)Z")) + //#else slice = @Slice(from = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/Screen;wrapScreenError(Ljava/lang/Runnable;Ljava/lang/String;Ljava/lang/String;)V")) + //#endif ) private Screen essential$emoteWheelAllowsMovement(Screen screen) { if (screen instanceof EmoteWheel) { diff --git a/versions/1.20.1-fabric/src/main/java/gg/essential/mixins/transformers/compatibility/emf/Mixin_EMFAnimationEntityContext.java b/versions/1.20.1-fabric/src/main/java/gg/essential/mixins/transformers/compatibility/emf/Mixin_EMFAnimationEntityContext.java new file mode 100644 index 0000000..ec723d2 --- /dev/null +++ b/versions/1.20.1-fabric/src/main/java/gg/essential/mixins/transformers/compatibility/emf/Mixin_EMFAnimationEntityContext.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.mixins.transformers.compatibility.emf; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import gg.essential.mixins.impl.client.entity.AbstractClientPlayerExt; +import it.unimi.dsi.fastutil.objects.ObjectSet; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.AbstractClientPlayerEntity; +import net.minecraft.entity.Entity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Pseudo; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; + +import java.util.UUID; + +@Pseudo +@Mixin(targets = "traben.entity_model_features.models.animation.EMFAnimationEntityContext") +public class Mixin_EMFAnimationEntityContext { + + @WrapOperation(method = "Ltraben/entity_model_features/models/animation/EMFAnimationEntityContext;isEntityAnimPaused()Z", at = @At(value = "INVOKE", target = "Lit/unimi/dsi/fastutil/objects/ObjectSet;contains(Ljava/lang/Object;)Z"), remap = false) + private static boolean pauseAnimationsWhenEmoting(ObjectSet instance, Object uuid, Operation original) { + return original.call(instance, uuid) || essential$isEntityEmoting(MinecraftClient.getInstance().world.getPlayerByUuid((UUID) uuid)); + } + + @Unique + private static boolean essential$isEntityEmoting(Entity entity) { + if (entity == null) return false; + if (!(entity instanceof AbstractClientPlayerEntity)) return false; + AbstractClientPlayerExt abstractClientPlayerEntity = (AbstractClientPlayerExt) entity; + return abstractClientPlayerEntity.isPoseModified(); + } +} diff --git a/versions/1.20.2-fabric/src/main/java/gg/essential/mixins/transformers/client/resources/Mixin_InstantSkinLoad.java b/versions/1.20.2-fabric/src/main/java/gg/essential/mixins/transformers/client/resources/Mixin_InstantSkinLoad.java index 6ca1ef1..3af6e3a 100644 --- a/versions/1.20.2-fabric/src/main/java/gg/essential/mixins/transformers/client/resources/Mixin_InstantSkinLoad.java +++ b/versions/1.20.2-fabric/src/main/java/gg/essential/mixins/transformers/client/resources/Mixin_InstantSkinLoad.java @@ -30,7 +30,7 @@ public class Mixin_InstantSkinLoad { // expiry time is the SkinTextures and a wrapper CompletableFuture. // As such, there should not be much harm in substantially increasing this value to prevent already loaded skins // from taking a frame (or more) to actually show up on newly created (UI3D)players. - //#if MC>12100 + //#if MC>12102 //$$ TODO verify above is still the case / if this is still necessary //#endif @ModifyArg(method = "", at = @At(value = "INVOKE", target = "Lcom/google/common/cache/CacheBuilder;expireAfterAccess(Ljava/time/Duration;)Lcom/google/common/cache/CacheBuilder;")) diff --git a/versions/1.21-fabric/gradle.properties b/versions/1.21-fabric/gradle.properties deleted file mode 100644 index 55f7854..0000000 --- a/versions/1.21-fabric/gradle.properties +++ /dev/null @@ -1,2 +0,0 @@ -essential.defaults.loom.minecraft=com.mojang:minecraft:1.21-rc1 -essential.defaults.loom.mappings=net.fabricmc:yarn:1.21-rc1+build.1:v2 diff --git a/versions/1.21.2-fabric/gradle.properties b/versions/1.21.2-fabric/gradle.properties new file mode 100644 index 0000000..3c59148 --- /dev/null +++ b/versions/1.21.2-fabric/gradle.properties @@ -0,0 +1,2 @@ +essential.defaults.loom.minecraft=com.mojang:minecraft:1.21.2-rc1 +essential.defaults.loom.mappings=net.fabricmc:yarn:1.21.2-rc1+build.1:v2 \ No newline at end of file diff --git a/versions/1.21.2-fabric/src/main/java/gg/essential/mixins/transformers/client/Mixin_GuiKeyTypedEvent.java b/versions/1.21.2-fabric/src/main/java/gg/essential/mixins/transformers/client/Mixin_GuiKeyTypedEvent.java new file mode 100644 index 0000000..230fd63 --- /dev/null +++ b/versions/1.21.2-fabric/src/main/java/gg/essential/mixins/transformers/client/Mixin_GuiKeyTypedEvent.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.mixins.transformers.client; + +import com.llamalad7.mixinextras.sugar.Local; +import gg.essential.Essential; +import gg.essential.event.gui.GuiKeyTypedEvent; +import net.minecraft.client.Keyboard; +import net.minecraft.client.gui.screen.Screen; +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; + +@Mixin(value = Keyboard.class, priority = 500) +public class Mixin_GuiKeyTypedEvent { + @Unique + private static void keyTyped(Screen screen, char typedChar, int keyCode, CallbackInfo ci) { + GuiKeyTypedEvent event = new GuiKeyTypedEvent(screen, typedChar, keyCode); + Essential.EVENT_BUS.post(event); + if (event.isCancelled()) { + ci.cancel(); + } + } + + @Inject(method = "onKey", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/Screen;keyPressed(III)Z"), cancellable = true) + private void onKeyTyped(CallbackInfo ci, @Local Screen screen, @Local(ordinal = 0, argsOnly = true) int key) { + keyTyped(screen, '\0', key, ci); + } + + @Inject(method = "onChar", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/Screen;charTyped(CI)Z"), cancellable = true) + private void onCharTyped( + CallbackInfo ci, + @Local Screen screen, + @Local(ordinal = 0, argsOnly = true) int codePoint + ) { + if (Character.isBmpCodePoint(codePoint)) { + keyTyped(screen, (char) codePoint, 0, ci); + } else if (Character.isValidCodePoint(codePoint)) { + keyTyped(screen, Character.highSurrogate(codePoint), 0, ci); + keyTyped(screen, Character.lowSurrogate(codePoint), 0, ci); + } + } +} diff --git a/versions/1.21.2-fabric/src/main/java/gg/essential/mixins/transformers/client/model/Mixin_PlayerEntityRenderStateExt.java b/versions/1.21.2-fabric/src/main/java/gg/essential/mixins/transformers/client/model/Mixin_PlayerEntityRenderStateExt.java new file mode 100644 index 0000000..8ef6a42 --- /dev/null +++ b/versions/1.21.2-fabric/src/main/java/gg/essential/mixins/transformers/client/model/Mixin_PlayerEntityRenderStateExt.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.mixins.transformers.client.model; + +import gg.essential.mixins.impl.client.model.PlayerEntityRenderStateExt; +import net.minecraft.client.network.AbstractClientPlayerEntity; +import net.minecraft.client.render.entity.state.PlayerEntityRenderState; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; + +@Mixin(PlayerEntityRenderState.class) +public abstract class Mixin_PlayerEntityRenderStateExt implements PlayerEntityRenderStateExt { + @Unique + private AbstractClientPlayerEntity entity; + @Unique + private float tickDelta; + + @Override + public AbstractClientPlayerEntity essential$getEntity() { + return this.entity; + } + + @Override + public void essential$setEntity(AbstractClientPlayerEntity entity) { + this.entity = entity; + } + + @Override + public float essential$getTickDelta() { + return this.tickDelta; + } + + @Override + public void essential$setTickDelta(float tickDelta) { + this.tickDelta = tickDelta; + } +} diff --git a/versions/1.21.2-fabric/src/main/java/gg/essential/mixins/transformers/client/model/Mixin_PlayerEntityRenderStateExt_UpdateRenderState.java b/versions/1.21.2-fabric/src/main/java/gg/essential/mixins/transformers/client/model/Mixin_PlayerEntityRenderStateExt_UpdateRenderState.java new file mode 100644 index 0000000..e02360e --- /dev/null +++ b/versions/1.21.2-fabric/src/main/java/gg/essential/mixins/transformers/client/model/Mixin_PlayerEntityRenderStateExt_UpdateRenderState.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.mixins.transformers.client.model; + +import gg.essential.mixins.impl.client.model.PlayerEntityRenderStateExt; +import net.minecraft.client.network.AbstractClientPlayerEntity; +import net.minecraft.client.render.entity.PlayerEntityRenderer; +import net.minecraft.client.render.entity.state.PlayerEntityRenderState; +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(PlayerEntityRenderer.class) +public abstract class Mixin_PlayerEntityRenderStateExt_UpdateRenderState { + @Inject(method = "updateRenderState(Lnet/minecraft/client/network/AbstractClientPlayerEntity;Lnet/minecraft/client/render/entity/state/PlayerEntityRenderState;F)V", at = @At("HEAD")) + private void updateEssentialRenderState(AbstractClientPlayerEntity entity, PlayerEntityRenderState state, float tickDelta, CallbackInfo ci) { + PlayerEntityRenderStateExt stateExt = (PlayerEntityRenderStateExt) state; + stateExt.essential$setEntity(entity); + stateExt.essential$setTickDelta(tickDelta); + } +} diff --git a/versions/1.21.2-fabric/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_ApplyPoseTransform_Cape.java b/versions/1.21.2-fabric/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_ApplyPoseTransform_Cape.java new file mode 100644 index 0000000..f8da7b7 --- /dev/null +++ b/versions/1.21.2-fabric/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_ApplyPoseTransform_Cape.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.mixins.transformers.client.renderer.entity; + +import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; +import com.llamalad7.mixinextras.sugar.Local; +import com.llamalad7.mixinextras.sugar.Share; +import com.llamalad7.mixinextras.sugar.ref.LocalRef; +import dev.folomeev.kotgl.matrix.vectors.Vec3; +import gg.essential.config.EssentialConfig; +import gg.essential.gui.common.EmulatedUI3DPlayer; +import gg.essential.mixins.impl.client.entity.AbstractClientPlayerExt; +import gg.essential.mixins.impl.client.model.CapePoseSupplier; +import gg.essential.mixins.impl.client.model.PlayerEntityRenderStateExt; +import gg.essential.model.backend.PlayerPose; +import gg.essential.model.backend.minecraft.PlayerPoseKt; +import net.minecraft.client.network.AbstractClientPlayerEntity; +import net.minecraft.client.model.ModelPart; +import net.minecraft.client.render.entity.feature.CapeFeatureRenderer; +import net.minecraft.client.render.entity.model.BipedEntityModel; +import net.minecraft.client.render.entity.state.PlayerEntityRenderState; +import net.minecraft.client.util.math.MatrixStack; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +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 static dev.folomeev.kotgl.matrix.vectors.Vectors.vec3; +import static dev.folomeev.kotgl.matrix.vectors.Vectors.vecZero; + +@Mixin(CapeFeatureRenderer.class) +public abstract class Mixin_ApplyPoseTransform_Cape implements CapePoseSupplier { + private static final String RENDER_LAYER = "render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/client/render/entity/state/PlayerEntityRenderState;FF)V"; + private static final String RENDER_CAPE = "Lnet/minecraft/client/render/entity/model/BipedEntityModel;render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumer;II)V"; + + @Shadow @Final private BipedEntityModel model; + + @Unique + private PlayerPose.Part renderedPose; + + @Inject(method = RENDER_LAYER, at = @At("HEAD")) + private void unsetRenderedPose(CallbackInfo ci) { + renderedPose = null; + } + + @WrapWithCondition(method = RENDER_LAYER, at = @At(value = "INVOKE", target = "Lnet/minecraft/client/util/math/MatrixStack;translate(FFF)V")) + private boolean captureTranslation(MatrixStack stack, float x, float y, float z, @Share("offset") LocalRef offset) { + offset.set(vec3(x * 16f, y * 16f, z * 16f)); + return true; + } + + @Inject(method = RENDER_LAYER, at = @At(value = "INVOKE", target = RENDER_CAPE)) + private void applyPoseTransform( + CallbackInfo ci, + @Local(argsOnly = true) PlayerEntityRenderState state, + @Share("offset") LocalRef offset + ) { + AbstractClientPlayerEntity player = ((PlayerEntityRenderStateExt) state).essential$getEntity(); + AbstractClientPlayerExt playerExt = (AbstractClientPlayerExt) player; + + if (EssentialConfig.INSTANCE.getDisableEmotes() && !(player instanceof EmulatedUI3DPlayer.EmulatedPlayer)) { + return; + } + + ModelPart bodyModel = this.model.body; + ModelPart capeModel = bodyModel.getChild("cape"); + + Vec3 extraOffset = offset.get(); + if (extraOffset == null) { + extraOffset = vecZero(); + } + + PlayerPose basePose = PlayerPoseKt.withCapePose(PlayerPose.Companion.neutral(), extraOffset, bodyModel, capeModel); + PlayerPose transformedPose = playerExt.getPoseManager().computePose(playerExt.getWearablesManager(), basePose); + + renderedPose = transformedPose.getCape(); + + if (basePose.equals(transformedPose)) { + return; + } + + // Apply our computed pose with all the animations that affect it + PlayerPoseKt.applyCapePose(transformedPose, extraOffset, bodyModel, capeModel); + } + + @Override + public @Nullable PlayerPose.Part getCapePose() { + return renderedPose; + } +} diff --git a/versions/1.21.2-fabric/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_DisableArmorRendering.java b/versions/1.21.2-fabric/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_DisableArmorRendering.java new file mode 100644 index 0000000..3fcec4d --- /dev/null +++ b/versions/1.21.2-fabric/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_DisableArmorRendering.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.mixins.transformers.client.renderer.entity; + +import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; +import com.llamalad7.mixinextras.sugar.Local; +import gg.essential.mixins.impl.client.model.PlayerEntityRenderStateExt; +import net.minecraft.client.network.AbstractClientPlayerEntity; +import net.minecraft.client.render.entity.state.BipedEntityRenderState; +import net.minecraft.client.util.math.MatrixStack; +import gg.essential.mixins.impl.client.renderer.entity.ArmorRenderingUtil; +import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.render.entity.feature.ArmorFeatureRenderer; +import net.minecraft.client.render.entity.model.BipedEntityModel; +import net.minecraft.entity.EquipmentSlot; +import net.minecraft.item.ItemStack; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(value = ArmorFeatureRenderer.class) +public abstract class Mixin_DisableArmorRendering, A extends BipedEntityModel> { + @WrapWithCondition( + method = "render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/client/render/entity/state/BipedEntityRenderState;FF)V", + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/entity/feature/ArmorFeatureRenderer;renderArmor(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;Lnet/minecraft/item/ItemStack;Lnet/minecraft/entity/EquipmentSlot;ILnet/minecraft/client/render/entity/model/BipedEntityModel;)V") + ) + private boolean essential$disableArmorRendering( + ArmorFeatureRenderer self, MatrixStack matrixStack, VertexConsumerProvider vertexConsumers, ItemStack stack, EquipmentSlot slot, int light, A armorModel, + @Local(argsOnly = true) S state + ) { + if (!(state instanceof PlayerEntityRenderStateExt)) return true; + AbstractClientPlayerEntity entity = ((PlayerEntityRenderStateExt) state).essential$getEntity(); + return !ArmorRenderingUtil.shouldDisableArmor(entity, slot.getEntitySlotId()); + } +} diff --git a/versions/1.21.2-fabric/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_Emissive_Elytra.java b/versions/1.21.2-fabric/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_Emissive_Elytra.java new file mode 100644 index 0000000..e2130a9 --- /dev/null +++ b/versions/1.21.2-fabric/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/Mixin_Emissive_Elytra.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.mixins.transformers.client.renderer.entity; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Local; +import gg.essential.mixins.impl.client.entity.AbstractClientPlayerExt; +import gg.essential.mixins.impl.client.model.PlayerEntityRenderStateExt; +import gg.essential.model.backend.minecraft.MinecraftRenderBackend; +import gg.essential.util.UIdentifier; +import net.minecraft.client.model.Model; +import net.minecraft.client.render.RenderLayer; +import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.render.entity.equipment.EquipmentRenderer; +import net.minecraft.client.render.entity.feature.ElytraFeatureRenderer; +import net.minecraft.client.render.entity.state.BipedEntityRenderState; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.item.ItemStack; +import net.minecraft.item.equipment.EquipmentModel; +import net.minecraft.util.Identifier; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +import static gg.essential.util.UIdentifierKt.toMC; + +@Mixin(ElytraFeatureRenderer.class) +public abstract class Mixin_Emissive_Elytra { + + private static final String RENDER_LAYER = "render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/client/render/entity/state/BipedEntityRenderState;FF)V"; + private static final String RENDER_ELYTRA = "Lnet/minecraft/client/render/entity/equipment/EquipmentRenderer;render(Lnet/minecraft/item/equipment/EquipmentModel$LayerType;Lnet/minecraft/util/Identifier;Lnet/minecraft/client/model/Model;Lnet/minecraft/item/ItemStack;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/util/Identifier;)V"; + + @WrapOperation(method = RENDER_LAYER, at = @At(value = "INVOKE", target = RENDER_ELYTRA)) + private void renderWithEmissiveLayer( + EquipmentRenderer equipmentRenderer, + EquipmentModel.LayerType layerType, + Identifier modelId, + Model model, + ItemStack stack, + MatrixStack matrices, + VertexConsumerProvider vertexConsumers, + int light, + @Nullable Identifier texture, + Operation original, + @Local(argsOnly = true) BipedEntityRenderState state + ) { + // Regular elytra + original.call( + equipmentRenderer, + layerType, + modelId, + model, + stack, + matrices, + vertexConsumers, + light, + texture + ); + + // Emissive layer + if (!(state instanceof PlayerEntityRenderStateExt)) { + return; + } + AbstractClientPlayerExt playerExt = (AbstractClientPlayerExt) ((PlayerEntityRenderStateExt) state).essential$getEntity(); + UIdentifier emissiveTexture = playerExt.getEmissiveCapeTexture(); + if (emissiveTexture == null) { + return; + } + Identifier emissiveTextureMc = toMC(emissiveTexture); + + RenderLayer vanillaLayer = RenderLayer.getArmorCutoutNoCull(emissiveTextureMc); + + original.call( + equipmentRenderer, + layerType, + modelId, + model, + stack, + matrices, + (VertexConsumerProvider) layer -> { + if (layer == vanillaLayer) { + return vertexConsumers.getBuffer(MinecraftRenderBackend.INSTANCE.getEmissiveArmorLayer(emissiveTextureMc)); + } else { + return MinecraftRenderBackend.NullMcVertexConsumer.INSTANCE; + } + }, + light, + emissiveTextureMc + ); + } +} diff --git a/src/main/kotlin/gg/essential/gui/modals/select/component/group.kt b/versions/1.21.2-fabric/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/equipment/EquipmentRendererAccessor.java similarity index 53% rename from src/main/kotlin/gg/essential/gui/modals/select/component/group.kt rename to versions/1.21.2-fabric/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/equipment/EquipmentRendererAccessor.java index 581e1f7..ef844d0 100644 --- a/src/main/kotlin/gg/essential/gui/modals/select/component/group.kt +++ b/versions/1.21.2-fabric/src/main/java/gg/essential/mixins/transformers/client/renderer/entity/equipment/EquipmentRendererAccessor.java @@ -9,16 +9,15 @@ * commercialize, or otherwise exploit, or create derivative works based * upon, this file or any other in this repository, all of which is reserved by Essential. */ -package gg.essential.gui.modals.select.component +package gg.essential.mixins.transformers.client.renderer.entity.equipment; -import gg.essential.elementa.dsl.effect -import gg.essential.gui.common.shadow.ShadowEffect -import gg.essential.gui.friends.previews.ChannelPreview -import gg.essential.gui.layoutdsl.LayoutScope -import gg.essential.gui.layoutdsl.Modifier +import net.minecraft.client.render.entity.equipment.EquipmentModelLoader; +import net.minecraft.client.render.entity.equipment.EquipmentRenderer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; -fun LayoutScope.groupIcon(id: Long, modifier: Modifier = Modifier) { - val image = ChannelPreview.newGroupIcon(id).effect(ShadowEffect()) - - image(modifier) +@Mixin(EquipmentRenderer.class) +public interface EquipmentRendererAccessor { + @Accessor + EquipmentModelLoader getEquipmentModelLoader(); } diff --git a/versions/1.21.2-fabric/src/main/java/gg/essential/mixins/transformers/compatibility/vanilla/Mixin_FixArrowsStuckInUnusedPlayerModelPart.java b/versions/1.21.2-fabric/src/main/java/gg/essential/mixins/transformers/compatibility/vanilla/Mixin_FixArrowsStuckInUnusedPlayerModelPart.java new file mode 100644 index 0000000..37db0a0 --- /dev/null +++ b/versions/1.21.2-fabric/src/main/java/gg/essential/mixins/transformers/compatibility/vanilla/Mixin_FixArrowsStuckInUnusedPlayerModelPart.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.mixins.transformers.compatibility.vanilla; + +import gg.essential.mixins.DummyTarget; +import org.spongepowered.asm.mixin.Mixin; + +// Issue has been fixed. The cape feature renderer now has its own model. +@Mixin(DummyTarget.class) +public abstract class Mixin_FixArrowsStuckInUnusedPlayerModelPart {} diff --git a/versions/1.21.2-fabric/src/main/java/gg/essential/mixins/transformers/cosmetics/skinmask/Mixin_ApplyToHandRenderer.java b/versions/1.21.2-fabric/src/main/java/gg/essential/mixins/transformers/cosmetics/skinmask/Mixin_ApplyToHandRenderer.java new file mode 100644 index 0000000..b5c4483 --- /dev/null +++ b/versions/1.21.2-fabric/src/main/java/gg/essential/mixins/transformers/cosmetics/skinmask/Mixin_ApplyToHandRenderer.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.mixins.transformers.cosmetics.skinmask; + +import gg.essential.mixins.impl.client.entity.AbstractClientPlayerExt; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.client.render.entity.PlayerEntityRenderer; +import net.minecraft.util.Identifier; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyVariable; + +@Mixin(PlayerEntityRenderer.class) +public abstract class Mixin_ApplyToHandRenderer { + @ModifyVariable(method = "renderArm", at = @At("HEAD"), argsOnly = true) + private Identifier applyCosmeticsSkinMaskToSleeve(Identifier skin) { + ClientPlayerEntity player = MinecraftClient.getInstance().player; + if (player == null) return skin; + return ((AbstractClientPlayerExt) player).applyEssentialCosmeticsMask(skin); + } +} diff --git a/versions/1.21.2-fabric/src/main/java/gg/essential/mixins/transformers/events/Mixin_GuiClickEvent.java b/versions/1.21.2-fabric/src/main/java/gg/essential/mixins/transformers/events/Mixin_GuiClickEvent.java new file mode 100644 index 0000000..067f3bc --- /dev/null +++ b/versions/1.21.2-fabric/src/main/java/gg/essential/mixins/transformers/events/Mixin_GuiClickEvent.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.mixins.transformers.events; + +import com.llamalad7.mixinextras.sugar.Local; +import gg.essential.Essential; +import gg.essential.event.gui.GuiClickEvent; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.Mouse; +import net.minecraft.client.gui.screen.Screen; +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(Mouse.class) +public abstract class Mixin_GuiClickEvent { + @Inject(method = "onMouseButton", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/Screen;mouseClicked(DDI)Z"), cancellable = true) + private void onMouseClicked( + CallbackInfo ci, + @Local(ordinal = 0) Screen screen, + @Local(ordinal = 0, argsOnly = true) int mouseButton, + @Local(ordinal = 0) double mouseX, + @Local(ordinal = 1) double mouseY + ) { + GuiClickEvent event = new GuiClickEvent(mouseX, mouseY, mouseButton, screen); + Essential.EVENT_BUS.post(event); + if (event.isCancelled() || MinecraftClient.getInstance().currentScreen != screen) { + ci.cancel(); + } + } +} diff --git a/versions/1.21.2-fabric/src/main/java/gg/essential/mixins/transformers/events/Mixin_GuiClickEvent_Priority.java b/versions/1.21.2-fabric/src/main/java/gg/essential/mixins/transformers/events/Mixin_GuiClickEvent_Priority.java new file mode 100644 index 0000000..88e3ecd --- /dev/null +++ b/versions/1.21.2-fabric/src/main/java/gg/essential/mixins/transformers/events/Mixin_GuiClickEvent_Priority.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +// 1.21.2 and above +package gg.essential.mixins.transformers.events; + +import com.llamalad7.mixinextras.sugar.Local; +import gg.essential.Essential; +import gg.essential.event.gui.GuiClickEvent; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.Mouse; +import net.minecraft.client.gui.screen.Screen; +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(value = Mouse.class, priority = 500) +public abstract class Mixin_GuiClickEvent_Priority { + @Inject(method = "onMouseButton", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/Screen;mouseClicked(DDI)Z"), cancellable = true) + private void onMouseClicked( + CallbackInfo ci, + @Local(ordinal = 0) Screen screen, + @Local(ordinal = 0, argsOnly = true) int mouseButton, + @Local(ordinal = 0) double mouseX, + @Local(ordinal = 1) double mouseY + ) { + GuiClickEvent event = new GuiClickEvent.Priority(mouseX, mouseY, mouseButton, screen); + Essential.EVENT_BUS.post(event); + if (event.isCancelled() || MinecraftClient.getInstance().currentScreen != screen) { + ci.cancel(); + } + } +} diff --git a/versions/1.21.2-fabric/src/main/java/gg/essential/mixins/transformers/events/Mixin_GuiMouseReleaseEvent.java b/versions/1.21.2-fabric/src/main/java/gg/essential/mixins/transformers/events/Mixin_GuiMouseReleaseEvent.java new file mode 100644 index 0000000..14693d4 --- /dev/null +++ b/versions/1.21.2-fabric/src/main/java/gg/essential/mixins/transformers/events/Mixin_GuiMouseReleaseEvent.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.mixins.transformers.events; + +import com.llamalad7.mixinextras.sugar.Local; +import gg.essential.Essential; +import gg.essential.event.gui.GuiMouseReleaseEvent; +import net.minecraft.client.Mouse; +import net.minecraft.client.gui.screen.Screen; +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(Mouse.class) +public class Mixin_GuiMouseReleaseEvent { + @Inject(method = "onMouseButton", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/Screen;mouseReleased(DDI)Z")) + private void onMouseClicked(CallbackInfo ci, @Local(ordinal = 0) Screen screen) { + Essential.EVENT_BUS.post(new GuiMouseReleaseEvent(screen)); + } +} diff --git a/versions/1.8.9-forge/src/main/java/gg/essential/mixins/transformers/client/settings/Mixin_UnbindConflictingKeybinds_OnChange.java b/versions/1.8.9-forge/src/main/java/gg/essential/mixins/transformers/client/settings/Mixin_UnbindConflictingKeybinds_OnChange.java new file mode 100644 index 0000000..c0a5b07 --- /dev/null +++ b/versions/1.8.9-forge/src/main/java/gg/essential/mixins/transformers/client/settings/Mixin_UnbindConflictingKeybinds_OnChange.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2024 ModCore Inc. All rights reserved. + * + * This code is part of ModCore Inc.'s Essential Mod repository and is protected + * under copyright registration # TX0009138511. For the full license, see: + * https://github.com/EssentialGG/Essential/blob/main/LICENSE + * + * You may not use, copy, reproduce, modify, sell, license, distribute, + * commercialize, or otherwise exploit, or create derivative works based + * upon, this file or any other in this repository, all of which is reserved by Essential. + */ +package gg.essential.mixins.transformers.client.settings; + +import gg.essential.mixins.DummyTarget; +import org.spongepowered.asm.mixin.Mixin; + +// Functions in 1.12 and newer +@Mixin(DummyTarget.class) +public class Mixin_UnbindConflictingKeybinds_OnChange {} diff --git a/versions/1.8.9-forge/src/main/java/gg/essential/mixins/transformers/client/settings/Mixin_UnbindConflictingKeybinds.java b/versions/1.8.9-forge/src/main/java/gg/essential/mixins/transformers/client/settings/Mixin_UnbindConflictingKeybinds_OnLoad.java similarity index 92% rename from versions/1.8.9-forge/src/main/java/gg/essential/mixins/transformers/client/settings/Mixin_UnbindConflictingKeybinds.java rename to versions/1.8.9-forge/src/main/java/gg/essential/mixins/transformers/client/settings/Mixin_UnbindConflictingKeybinds_OnLoad.java index bfa8fad..0e39d9d 100644 --- a/versions/1.8.9-forge/src/main/java/gg/essential/mixins/transformers/client/settings/Mixin_UnbindConflictingKeybinds.java +++ b/versions/1.8.9-forge/src/main/java/gg/essential/mixins/transformers/client/settings/Mixin_UnbindConflictingKeybinds_OnLoad.java @@ -16,4 +16,4 @@ // Functions in 1.12 and newer @Mixin(DummyTarget.class) -public class Mixin_UnbindConflictingKeybinds {} +public class Mixin_UnbindConflictingKeybinds_OnLoad {} diff --git a/versions/api/1.21-fabric/gradle.properties b/versions/api/1.21-fabric/gradle.properties deleted file mode 100644 index 55f7854..0000000 --- a/versions/api/1.21-fabric/gradle.properties +++ /dev/null @@ -1,2 +0,0 @@ -essential.defaults.loom.minecraft=com.mojang:minecraft:1.21-rc1 -essential.defaults.loom.mappings=net.fabricmc:yarn:1.21-rc1+build.1:v2 diff --git a/versions/api/1.21.2-fabric/gradle.properties b/versions/api/1.21.2-fabric/gradle.properties new file mode 100644 index 0000000..3c59148 --- /dev/null +++ b/versions/api/1.21.2-fabric/gradle.properties @@ -0,0 +1,2 @@ +essential.defaults.loom.minecraft=com.mojang:minecraft:1.21.2-rc1 +essential.defaults.loom.mappings=net.fabricmc:yarn:1.21.2-rc1+build.1:v2 \ No newline at end of file