diff --git a/build.gradle b/build.gradle index 29494be..7630d2c 100644 --- a/build.gradle +++ b/build.gradle @@ -1,8 +1,10 @@ plugins { + id 'java' id 'eclipse' id 'idea' id 'maven-publish' id 'net.minecraftforge.gradle' version '[6.0,6.2)' + id 'org.spongepowered.mixin' version '0.7.+' } version = mod_version @@ -26,11 +28,8 @@ minecraft { workingDirectory project.file('run') property 'forge.logging.markers', 'REGISTRIES' property 'forge.logging.console.level', 'debug' - - // IMPORTANT: allow Mixin remapping - property 'mixin.env.remapRefMap', 'true' - property 'mixin.env.refMapRemappingFile', "${projectDir}/build/createSrgToMcp/output.srg" - + property "mixin.env.remapRefMap", "true" + property "mixin.env.refMapRemappingFile", file("$buildDir/createSrgToMcp/output.srg").absolutePath mods { "${mod_id}" { source sourceSets.main @@ -40,6 +39,9 @@ minecraft { client { property 'forge.enabledGameTestNamespaces', mod_id + property "mixin.debug.verbose", "true" + property "mixin.debug.export", "true" + property "mixin.dumpTargetOnFailure", "true" } server { property 'forge.enabledGameTestNamespaces', mod_id @@ -87,11 +89,11 @@ repositories { dependencies { minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}" compileOnly fg.deobf("net.fretux.ascend:Ascend:2.1.1") - implementation fg.deobf("net.fretux.ascend:Ascend:2.1.1") - + runtimeOnly fg.deobf("net.fretux.ascend:Ascend:2.1.1") + // Iron's Spellbooks compileOnly fg.deobf("io.redspace.ironsspellbooks:irons_spellbooks:${irons_spells_version}:api") - implementation fg.deobf("io.redspace.ironsspellbooks:irons_spellbooks:${irons_spells_version}") + runtimeOnly fg.deobf("io.redspace.ironsspellbooks:irons_spellbooks:${irons_spells_version}") // GECKOLIB implementation fg.deobf("software.bernie.geckolib:geckolib-forge-${geckolib_version}") @@ -106,22 +108,31 @@ dependencies { // Curios compileOnly fg.deobf("top.theillusivec4.curios:curios-forge:${curios_version}:api") implementation fg.deobf("top.theillusivec4.curios:curios-forge:${curios_version}") + + annotationProcessor 'org.spongepowered:mixin:0.8.5:processor' +} + +mixin { + add sourceSets.main, "mixins.mindmotion.refmap.json" + debug.verbose = true + debug.export = true + dumpTargetOnFailure = true } tasks.named('processResources', ProcessResources).configure { var replaceProperties = [ - minecraft_version : minecraft_version, - minecraft_version_range : minecraft_version_range, - forge_version : forge_version, - forge_version_range : forge_version_range, - loader_version_range : loader_version_range, - mod_id : mod_id, - mod_name : mod_name, - mod_license : mod_license, - mod_version : mod_version, - mod_authors : mod_authors, - mod_description : mod_description, + minecraft_version : minecraft_version, + minecraft_version_range: minecraft_version_range, + forge_version : forge_version, + forge_version_range : forge_version_range, + loader_version_range : loader_version_range, + mod_id : mod_id, + mod_name : mod_name, + mod_license : mod_license, + mod_version : mod_version, + mod_authors : mod_authors, + mod_description : mod_description, ] inputs.properties replaceProperties diff --git a/gradle.properties b/gradle.properties index 8ca0560..5f0d201 100644 --- a/gradle.properties +++ b/gradle.properties @@ -52,7 +52,7 @@ mod_name=Mind & Motion # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=All Rights Reserved # The mod version. See https://semver.org/ -mod_version=1.2.2 +mod_version=1.2.3 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/gradle/gradle-daemon-jvm.properties b/gradle/gradle-daemon-jvm.properties index ef4314f..858feb7 100644 --- a/gradle/gradle-daemon-jvm.properties +++ b/gradle/gradle-daemon-jvm.properties @@ -1,2 +1,2 @@ #This file is generated by updateDaemonJvm -toolchainVersion=22 +toolchainVersion=17 diff --git a/src/main/java/net/fretux/mindmotion/client/effects/IronsSpellbooksCompatMM.java b/src/main/java/net/fretux/mindmotion/client/effects/IronsSpellbooksCompatMM.java index 9419f27..6d21ab5 100644 --- a/src/main/java/net/fretux/mindmotion/client/effects/IronsSpellbooksCompatMM.java +++ b/src/main/java/net/fretux/mindmotion/client/effects/IronsSpellbooksCompatMM.java @@ -1,6 +1,5 @@ package net.fretux.mindmotion.client.effects; -import io.redspace.ironsspellbooks.api.registry.AttributeRegistry; import net.fretux.mindmotion.ConfigMM; import net.fretux.mindmotion.player.PlayerCapabilityProvider; import net.minecraft.world.entity.ai.attributes.Attribute; @@ -9,38 +8,65 @@ import net.minecraft.world.entity.ai.attributes.AttributeModifier; import net.minecraft.world.entity.player.Player; import net.minecraftforge.fml.ModList; +import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.util.UUID; public final class IronsSpellbooksCompatMM { + private static final UUID TEMPO_MANA_REGEN_UUID = + UUID.fromString("de4f16ad-73b3-4b99-bf77-44c9878222aa"); + private static final String IRONS_MODID = "irons_spellbooks"; + private static final String ATTR_REGISTRY_CLASS = "io.redspace.ironsspellbooks.api.registry.AttributeRegistry"; + private static final String MANA_REGEN_FIELD = "MANA_REGEN"; + private static boolean resolved = false; + private static boolean available = false; + private static Object manaRegenHolder = null; + private static Method holderGetMethod = null; + + private IronsSpellbooksCompatMM() { + } + public static int getTempoPerPercent() { return ConfigMM.COMMON.TEMPO_PER_MANA_REGEN_PERCENT.get(); } - - private static final UUID TEMPO_MANA_REGEN_UUID = - UUID.fromString("de4f16ad-73b3-4b99-bf77-44c9878222aa"); - - private IronsSpellbooksCompatMM() {} public static boolean isLoaded() { - return ModList.get().isLoaded("irons_spellbooks"); + return ModList.get().isLoaded(IRONS_MODID); } + public static void applyTempo(Player player) { if (!isLoaded()) return; + resolveOnce(); + if (!available) return; player.getCapability(PlayerCapabilityProvider.TEMPO).ifPresent(tempoCap -> { int tempo = tempoCap.getTempo(); applyTempoToManaRegen(player, tempo); }); } + private static void resolveOnce() { + if (resolved) return; + resolved = true; + try { + Class attrReg = Class.forName(ATTR_REGISTRY_CLASS); + Field manaRegenField = attrReg.getField(MANA_REGEN_FIELD); + manaRegenHolder = manaRegenField.get(null); + if (manaRegenHolder == null) return; + holderGetMethod = manaRegenHolder.getClass().getMethod("get"); + available = true; + } catch (Throwable t) { + available = false; + } + } + public static void applyTempoToManaRegen(Player player, int tempo) { - Attribute manaRegenAttr = (Attribute) AttributeRegistry.MANA_REGEN.get(); + if (!available) return; + Attribute manaRegenAttr = getManaRegenAttribute(); if (manaRegenAttr == null) return; AttributeInstance inst = player.getAttribute(manaRegenAttr); if (inst == null) return; inst.removeModifier(TEMPO_MANA_REGEN_UUID); - if (tempo <= 0) { - return; - } + if (tempo <= 0) return; double bonusPercent = (double) tempo / getTempoPerPercent(); double multiplier = bonusPercent / 100.0; inst.addTransientModifier(new AttributeModifier( @@ -50,4 +76,13 @@ public final class IronsSpellbooksCompatMM { AttributeModifier.Operation.MULTIPLY_BASE )); } + + private static Attribute getManaRegenAttribute() { + try { + Object attrObj = holderGetMethod.invoke(manaRegenHolder); + return (attrObj instanceof Attribute a) ? a : null; + } catch (Throwable t) { + return null; + } + } } \ No newline at end of file diff --git a/src/main/java/net/fretux/mindmotion/client/effects/SanityShaderHandler.java b/src/main/java/net/fretux/mindmotion/client/effects/SanityShaderHandler.java index ff272ba..47da911 100644 --- a/src/main/java/net/fretux/mindmotion/client/effects/SanityShaderHandler.java +++ b/src/main/java/net/fretux/mindmotion/client/effects/SanityShaderHandler.java @@ -1,8 +1,10 @@ package net.fretux.mindmotion.client.effects; +import com.mojang.logging.LogUtils; import net.fretux.mindmotion.ConfigMM; import net.fretux.mindmotion.client.ClientData; import net.fretux.mindmotion.client.shader.VentShaderHandler; +import net.fretux.mindmotion.mixin.PostChainAccessor; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.PostChain; import net.minecraft.client.renderer.PostPass; @@ -13,29 +15,34 @@ import net.minecraftforge.client.event.RenderLevelStageEvent; import net.minecraftforge.event.entity.player.PlayerEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.util.ObfuscationReflectionHelper; +import org.slf4j.Logger; import java.lang.reflect.Field; import java.util.List; -@Mod.EventBusSubscriber(modid = "mindmotion", value = Dist.CLIENT) +@Mod.EventBusSubscriber(modid = "mindmotion", value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.FORGE) public class SanityShaderHandler { private static final ResourceLocation DESAT_EFFECT = - new ResourceLocation("mindmotion", "shaders/post/desaturate.json"); + ResourceLocation.fromNamespaceAndPath("mindmotion", "shaders/post/desaturate.json"); private static float currentStrength = 0f; - private static Field passesField; + private static final Logger LOGGER = LogUtils.getLogger(); + private static final Field PASSES_FIELD = + ObfuscationReflectionHelper.findField(PostChain.class, "f_110009_"); - private static List getPasses(PostChain chain) throws IllegalAccessException, NoSuchFieldException { - if (passesField == null) { - passesField = PostChain.class.getDeclaredField("passes"); - passesField.setAccessible(true); + @SuppressWarnings("unchecked") + private static List safeGetPasses(PostChain chain) { + try { + return (List) PASSES_FIELD.get(chain); + } catch (IllegalAccessException e) { + LOGGER.error("Failed to access PostChain.passes", e); + return List.of(); } - @SuppressWarnings("unchecked") - List passes = (List) passesField.get(chain); - return passes; } + @SubscribeEvent public static void onRenderWorld(RenderLevelStageEvent event) { Minecraft mc = Minecraft.getInstance(); @@ -48,6 +55,7 @@ public class SanityShaderHandler { float desatMultiplier = ConfigMM.CLIENT.SANITY_DESAT_MULTIPLIER.get().floatValue(); if (event.getStage() != RenderLevelStageEvent.Stage.AFTER_LEVEL) return; if (mc.player == null || mc.level == null) return; + LOGGER.debug("SanityShaderHandler tick stage={}", event.getStage()); float sanity = ClientData.SANITY; float insanity = ClientData.INSANITY; float maxSanity = ClientData.MAX_SANITY; @@ -71,15 +79,34 @@ public class SanityShaderHandler { if (mc.gameRenderer.currentEffect() == null) { try { mc.gameRenderer.loadEffect(DESAT_EFFECT); + PostChain chain = mc.gameRenderer.currentEffect(); + LOGGER.info("Loaded post chain = {}", chain != null); + if (chain != null) { + try { + for (PostPass pass : safeGetPasses(chain)) { + EffectInstance fx = pass.getEffect(); + LOGGER.info("pass fx={}", fx != null ? fx.getName() : "null"); + if (fx != null) { + LOGGER.info(" Desat uniform={}", fx.getUniform("Desat") != null); + } + } + } catch (Throwable t) { + LOGGER.error("Failed to read PostChain passes via reflection", t); + } + } } catch (Exception e) { - e.printStackTrace(); + LOGGER.error("Failed to read PostChain passes via reflection", e); return; } } PostChain chain = mc.gameRenderer.currentEffect(); + LOGGER.info("effect class={}, accessorApplied={}", + chain == null ? "null" : chain.getClass().getName(), + chain instanceof PostChainAccessor + ); if (chain == null) return; try { - for (PostPass pass : getPasses(chain)) { + for (PostPass pass : safeGetPasses(chain)) { EffectInstance effect = pass.getEffect(); if (effect == null) continue; var uDesat = effect.getUniform("Desat"); @@ -144,7 +171,7 @@ public class SanityShaderHandler { } } } catch (Exception e) { - e.printStackTrace(); + LOGGER.info("Error loading post chain{}", e.getMessage()); } } diff --git a/src/main/java/net/fretux/mindmotion/mixin/PostChainAccessor.java b/src/main/java/net/fretux/mindmotion/mixin/PostChainAccessor.java new file mode 100644 index 0000000..b274e27 --- /dev/null +++ b/src/main/java/net/fretux/mindmotion/mixin/PostChainAccessor.java @@ -0,0 +1,14 @@ +package net.fretux.mindmotion.mixin; + +import net.minecraft.client.renderer.PostChain; +import net.minecraft.client.renderer.PostPass; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.List; + +@Mixin(PostChain.class) +public interface PostChainAccessor { + @Accessor("passes") + List mindmotion$getPasses(); +} \ No newline at end of file diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index 80795f1..9880fe3 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -40,7 +40,7 @@ authors="${mod_authors}" #optional # NONE means that no display test is set on your mod. You need to do this yourself, see IExtensionPoint.DisplayTest for more information. You can define any scheme you wish with this value. # IMPORTANT NOTE: this is NOT an instruction as to which environments (CLIENT or DEDICATED SERVER) your mod loads on. Your mod should load (and maybe do nothing!) whereever it finds itself. #displayTest="MATCH_VERSION" # if nothing is specified, MATCH_VERSION is the default when clientSideOnly=false, otherwise IGNORE_ALL_VERSION when clientSideOnly=true (#optional) - +mixinConfigs="mixins.mindmotion.json" # The description text for the mod (multi line!) (#mandatory) description='''${mod_description}''' # A dependency - use the . to indicate dependency for a specific modid. Dependencies are optional. diff --git a/src/main/resources/assets/mindmotion/shaders/program/desaturate.vsh b/src/main/resources/assets/mindmotion/shaders/program/desaturate.vsh index 8aaeb53..c3edbb8 100644 --- a/src/main/resources/assets/mindmotion/shaders/program/desaturate.vsh +++ b/src/main/resources/assets/mindmotion/shaders/program/desaturate.vsh @@ -6,23 +6,25 @@ uniform mat4 ProjMat; uniform vec2 OutSize; uniform float ShakeStrength; uniform float Time; -out vec2 oneTexel; +out vec2 oneTexel; out vec2 texCoord; void main() { oneTexel = 1.0 / OutSize; - // screen-shake offset in *pixels* - float px = sin(Time * 25.0 + Position.x * 5.0) * ShakeStrength * 20.0; - float py = cos(Time * 20.0 + Position.y * 7.0) * ShakeStrength * 20.0; - - // convert from pixels → normalized coordinates - vec2 offset = vec2(px / OutSize.x, py / OutSize.y); - - vec2 shaken = Position.xy + offset; - - texCoord = shaken / OutSize; - - vec4 outPos = ProjMat * vec4(shaken, 0.0, 1.0); + vec2 uv = Position.xy; + vec2 posPx = Position.xy; + if (Position.x > 2.0 || Position.y > 2.0) { + uv = Position.xy / OutSize; + posPx = Position.xy; + } else { + uv = Position.xy; + posPx = Position.xy * OutSize; + } + float px = sin(Time * 25.0 + posPx.x * 0.01) * ShakeStrength * 20.0; + float py = cos(Time * 20.0 + posPx.y * 0.01) * ShakeStrength * 20.0; + vec2 uvOffset = vec2(px / OutSize.x, py / OutSize.y); + texCoord = uv + uvOffset; + vec4 outPos = ProjMat * vec4(posPx, 0.0, 1.0); gl_Position = vec4(outPos.xy, 0.0, 1.0); } \ No newline at end of file diff --git a/src/main/resources/mixins.mindmotion.json b/src/main/resources/mixins.mindmotion.json new file mode 100644 index 0000000..eb900e2 --- /dev/null +++ b/src/main/resources/mixins.mindmotion.json @@ -0,0 +1,10 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "net.fretux.mindmotion.mixin", + "refmap": "mixins.mindmotion.refmap.json", + "compatibilityLevel": "JAVA_17", + "client": [ + "PostChainAccessor" + ] +} \ No newline at end of file