Update mod version and add Mixin support

This commit is contained in:
2026-01-06 20:20:55 +01:00
parent 0d87401849
commit f8df852136
9 changed files with 158 additions and 59 deletions
+19 -8
View File
@@ -1,8 +1,10 @@
plugins { plugins {
id 'java'
id 'eclipse' id 'eclipse'
id 'idea' id 'idea'
id 'maven-publish' id 'maven-publish'
id 'net.minecraftforge.gradle' version '[6.0,6.2)' id 'net.minecraftforge.gradle' version '[6.0,6.2)'
id 'org.spongepowered.mixin' version '0.7.+'
} }
version = mod_version version = mod_version
@@ -26,11 +28,8 @@ minecraft {
workingDirectory project.file('run') workingDirectory project.file('run')
property 'forge.logging.markers', 'REGISTRIES' property 'forge.logging.markers', 'REGISTRIES'
property 'forge.logging.console.level', 'debug' property 'forge.logging.console.level', 'debug'
property "mixin.env.remapRefMap", "true"
// IMPORTANT: allow Mixin remapping property "mixin.env.refMapRemappingFile", file("$buildDir/createSrgToMcp/output.srg").absolutePath
property 'mixin.env.remapRefMap', 'true'
property 'mixin.env.refMapRemappingFile', "${projectDir}/build/createSrgToMcp/output.srg"
mods { mods {
"${mod_id}" { "${mod_id}" {
source sourceSets.main source sourceSets.main
@@ -40,6 +39,9 @@ minecraft {
client { client {
property 'forge.enabledGameTestNamespaces', mod_id property 'forge.enabledGameTestNamespaces', mod_id
property "mixin.debug.verbose", "true"
property "mixin.debug.export", "true"
property "mixin.dumpTargetOnFailure", "true"
} }
server { server {
property 'forge.enabledGameTestNamespaces', mod_id property 'forge.enabledGameTestNamespaces', mod_id
@@ -87,11 +89,11 @@ repositories {
dependencies { dependencies {
minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}" minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}"
compileOnly fg.deobf("net.fretux.ascend:Ascend:2.1.1") 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 // Iron's Spellbooks
compileOnly fg.deobf("io.redspace.ironsspellbooks:irons_spellbooks:${irons_spells_version}:api") 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 // GECKOLIB
implementation fg.deobf("software.bernie.geckolib:geckolib-forge-${geckolib_version}") implementation fg.deobf("software.bernie.geckolib:geckolib-forge-${geckolib_version}")
@@ -106,13 +108,22 @@ dependencies {
// Curios // Curios
compileOnly fg.deobf("top.theillusivec4.curios:curios-forge:${curios_version}:api") compileOnly fg.deobf("top.theillusivec4.curios:curios-forge:${curios_version}:api")
implementation fg.deobf("top.theillusivec4.curios:curios-forge:${curios_version}") 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 { tasks.named('processResources', ProcessResources).configure {
var replaceProperties = [ var replaceProperties = [
minecraft_version : minecraft_version, minecraft_version : minecraft_version,
minecraft_version_range : minecraft_version_range, minecraft_version_range: minecraft_version_range,
forge_version : forge_version, forge_version : forge_version,
forge_version_range : forge_version_range, forge_version_range : forge_version_range,
loader_version_range : loader_version_range, loader_version_range : loader_version_range,
+1 -1
View File
@@ -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. # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default.
mod_license=All Rights Reserved mod_license=All Rights Reserved
# The mod version. See https://semver.org/ # 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. # 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. # This should match the base package used for the mod sources.
# See https://maven.apache.org/guides/mini/guide-naming-conventions.html # See https://maven.apache.org/guides/mini/guide-naming-conventions.html
+1 -1
View File
@@ -1,2 +1,2 @@
#This file is generated by updateDaemonJvm #This file is generated by updateDaemonJvm
toolchainVersion=22 toolchainVersion=17
@@ -1,6 +1,5 @@
package net.fretux.mindmotion.client.effects; package net.fretux.mindmotion.client.effects;
import io.redspace.ironsspellbooks.api.registry.AttributeRegistry;
import net.fretux.mindmotion.ConfigMM; import net.fretux.mindmotion.ConfigMM;
import net.fretux.mindmotion.player.PlayerCapabilityProvider; import net.fretux.mindmotion.player.PlayerCapabilityProvider;
import net.minecraft.world.entity.ai.attributes.Attribute; 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.minecraft.world.entity.player.Player;
import net.minecraftforge.fml.ModList; import net.minecraftforge.fml.ModList;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.UUID; import java.util.UUID;
public final class IronsSpellbooksCompatMM { 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() { public static int getTempoPerPercent() {
return ConfigMM.COMMON.TEMPO_PER_MANA_REGEN_PERCENT.get(); 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() { public static boolean isLoaded() {
return ModList.get().isLoaded("irons_spellbooks"); return ModList.get().isLoaded(IRONS_MODID);
} }
public static void applyTempo(Player player) { public static void applyTempo(Player player) {
if (!isLoaded()) return; if (!isLoaded()) return;
resolveOnce();
if (!available) return;
player.getCapability(PlayerCapabilityProvider.TEMPO).ifPresent(tempoCap -> { player.getCapability(PlayerCapabilityProvider.TEMPO).ifPresent(tempoCap -> {
int tempo = tempoCap.getTempo(); int tempo = tempoCap.getTempo();
applyTempoToManaRegen(player, tempo); 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) { 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; if (manaRegenAttr == null) return;
AttributeInstance inst = player.getAttribute(manaRegenAttr); AttributeInstance inst = player.getAttribute(manaRegenAttr);
if (inst == null) return; if (inst == null) return;
inst.removeModifier(TEMPO_MANA_REGEN_UUID); inst.removeModifier(TEMPO_MANA_REGEN_UUID);
if (tempo <= 0) { if (tempo <= 0) return;
return;
}
double bonusPercent = (double) tempo / getTempoPerPercent(); double bonusPercent = (double) tempo / getTempoPerPercent();
double multiplier = bonusPercent / 100.0; double multiplier = bonusPercent / 100.0;
inst.addTransientModifier(new AttributeModifier( inst.addTransientModifier(new AttributeModifier(
@@ -50,4 +76,13 @@ public final class IronsSpellbooksCompatMM {
AttributeModifier.Operation.MULTIPLY_BASE 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;
}
}
} }
@@ -1,8 +1,10 @@
package net.fretux.mindmotion.client.effects; package net.fretux.mindmotion.client.effects;
import com.mojang.logging.LogUtils;
import net.fretux.mindmotion.ConfigMM; import net.fretux.mindmotion.ConfigMM;
import net.fretux.mindmotion.client.ClientData; import net.fretux.mindmotion.client.ClientData;
import net.fretux.mindmotion.client.shader.VentShaderHandler; import net.fretux.mindmotion.client.shader.VentShaderHandler;
import net.fretux.mindmotion.mixin.PostChainAccessor;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.PostChain; import net.minecraft.client.renderer.PostChain;
import net.minecraft.client.renderer.PostPass; import net.minecraft.client.renderer.PostPass;
@@ -13,28 +15,33 @@ import net.minecraftforge.client.event.RenderLevelStageEvent;
import net.minecraftforge.event.entity.player.PlayerEvent; import net.minecraftforge.event.entity.player.PlayerEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.util.ObfuscationReflectionHelper;
import org.slf4j.Logger;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.List; 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 { public class SanityShaderHandler {
private static final ResourceLocation DESAT_EFFECT = 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 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<PostPass> getPasses(PostChain chain) throws IllegalAccessException, NoSuchFieldException {
if (passesField == null) {
passesField = PostChain.class.getDeclaredField("passes");
passesField.setAccessible(true);
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
List<PostPass> passes = (List<PostPass>) passesField.get(chain); private static List<PostPass> safeGetPasses(PostChain chain) {
return passes; try {
return (List<PostPass>) PASSES_FIELD.get(chain);
} catch (IllegalAccessException e) {
LOGGER.error("Failed to access PostChain.passes", e);
return List.of();
} }
}
@SubscribeEvent @SubscribeEvent
public static void onRenderWorld(RenderLevelStageEvent event) { public static void onRenderWorld(RenderLevelStageEvent event) {
@@ -48,6 +55,7 @@ public class SanityShaderHandler {
float desatMultiplier = ConfigMM.CLIENT.SANITY_DESAT_MULTIPLIER.get().floatValue(); float desatMultiplier = ConfigMM.CLIENT.SANITY_DESAT_MULTIPLIER.get().floatValue();
if (event.getStage() != RenderLevelStageEvent.Stage.AFTER_LEVEL) return; if (event.getStage() != RenderLevelStageEvent.Stage.AFTER_LEVEL) return;
if (mc.player == null || mc.level == null) return; if (mc.player == null || mc.level == null) return;
LOGGER.debug("SanityShaderHandler tick stage={}", event.getStage());
float sanity = ClientData.SANITY; float sanity = ClientData.SANITY;
float insanity = ClientData.INSANITY; float insanity = ClientData.INSANITY;
float maxSanity = ClientData.MAX_SANITY; float maxSanity = ClientData.MAX_SANITY;
@@ -71,15 +79,34 @@ public class SanityShaderHandler {
if (mc.gameRenderer.currentEffect() == null) { if (mc.gameRenderer.currentEffect() == null) {
try { try {
mc.gameRenderer.loadEffect(DESAT_EFFECT); 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) { } catch (Exception e) {
e.printStackTrace(); LOGGER.error("Failed to read PostChain passes via reflection", e);
return; return;
} }
} }
PostChain chain = mc.gameRenderer.currentEffect(); PostChain chain = mc.gameRenderer.currentEffect();
LOGGER.info("effect class={}, accessorApplied={}",
chain == null ? "null" : chain.getClass().getName(),
chain instanceof PostChainAccessor
);
if (chain == null) return; if (chain == null) return;
try { try {
for (PostPass pass : getPasses(chain)) { for (PostPass pass : safeGetPasses(chain)) {
EffectInstance effect = pass.getEffect(); EffectInstance effect = pass.getEffect();
if (effect == null) continue; if (effect == null) continue;
var uDesat = effect.getUniform("Desat"); var uDesat = effect.getUniform("Desat");
@@ -144,7 +171,7 @@ public class SanityShaderHandler {
} }
} }
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); LOGGER.info("Error loading post chain{}", e.getMessage());
} }
} }
@@ -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<PostPass> mindmotion$getPasses();
}
+1 -1
View File
@@ -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. # 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. # 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) #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) # The description text for the mod (multi line!) (#mandatory)
description='''${mod_description}''' description='''${mod_description}'''
# A dependency - use the . to indicate dependency for a specific modid. Dependencies are optional. # A dependency - use the . to indicate dependency for a specific modid. Dependencies are optional.
@@ -6,23 +6,25 @@ uniform mat4 ProjMat;
uniform vec2 OutSize; uniform vec2 OutSize;
uniform float ShakeStrength; uniform float ShakeStrength;
uniform float Time; uniform float Time;
out vec2 oneTexel;
out vec2 oneTexel;
out vec2 texCoord; out vec2 texCoord;
void main() { void main() {
oneTexel = 1.0 / OutSize; oneTexel = 1.0 / OutSize;
// screen-shake offset in *pixels* vec2 uv = Position.xy;
float px = sin(Time * 25.0 + Position.x * 5.0) * ShakeStrength * 20.0; vec2 posPx = Position.xy;
float py = cos(Time * 20.0 + Position.y * 7.0) * ShakeStrength * 20.0; if (Position.x > 2.0 || Position.y > 2.0) {
uv = Position.xy / OutSize;
// convert from pixels → normalized coordinates posPx = Position.xy;
vec2 offset = vec2(px / OutSize.x, py / OutSize.y); } else {
uv = Position.xy;
vec2 shaken = Position.xy + offset; posPx = Position.xy * OutSize;
}
texCoord = shaken / 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;
vec4 outPos = ProjMat * vec4(shaken, 0.0, 1.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); gl_Position = vec4(outPos.xy, 0.0, 1.0);
} }
+10
View File
@@ -0,0 +1,10 @@
{
"required": true,
"minVersion": "0.8",
"package": "net.fretux.mindmotion.mixin",
"refmap": "mixins.mindmotion.refmap.json",
"compatibilityLevel": "JAVA_17",
"client": [
"PostChainAccessor"
]
}