diff --git a/gradle.properties b/gradle.properties index 6377ef4..329e4a0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,7 +14,7 @@ maven_group=com.odtheking archives_base_name=odin-animations # Mod Dependencies fabric_kotlin_version=1.13.8+kotlin.2.3.0 -fabric_api_version=0.138.3+1.21.10 +fabric_api_version=0.138.4+1.21.10 devauth_version=1.2.1 commodore_version=1.0.1 # Other Properties @@ -23,4 +23,4 @@ kotlin.code.style=official minecraft_lwjgl_version=3.3.3 # you can put here either version number (0.0.4) or commit hashes -odin_version=0.1.2 \ No newline at end of file +odin_version=0.1.2 diff --git a/src/main/java/com/odtheking/odinaddon/mixin/ItemInHandRendererMixin.java b/src/main/java/com/odtheking/odinaddon/mixin/ItemInHandRendererMixin.java index dd08626..40878e3 100644 --- a/src/main/java/com/odtheking/odinaddon/mixin/ItemInHandRendererMixin.java +++ b/src/main/java/com/odtheking/odinaddon/mixin/ItemInHandRendererMixin.java @@ -114,4 +114,4 @@ private float overrideAttackStrengthScale(float originalValue) { private void forceInstantItemSwap(ItemStack oldItem, ItemStack newItem, CallbackInfoReturnable cir) { if (Animations.getShouldNoEquipReset()) cir.setReturnValue(true); } -} \ No newline at end of file +} diff --git a/src/main/java/com/odtheking/odinaddon/mixin/LivingEntityMixin.java b/src/main/java/com/odtheking/odinaddon/mixin/LivingEntityMixin.java index ba5f99b..420fb56 100644 --- a/src/main/java/com/odtheking/odinaddon/mixin/LivingEntityMixin.java +++ b/src/main/java/com/odtheking/odinaddon/mixin/LivingEntityMixin.java @@ -1,6 +1,6 @@ package com.odtheking.odinaddon.mixin; -import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import com.llamalad7.mixinextras.injector.ModifyReturnValue; import com.odtheking.odinaddon.features.impl.render.Animations; import net.minecraft.world.entity.LivingEntity; import org.spongepowered.asm.mixin.Mixin; @@ -9,9 +9,9 @@ @Mixin(LivingEntity.class) public abstract class LivingEntityMixin { - @ModifyExpressionValue(method = "updateSwingTime", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;getCurrentSwingDuration()I")) - private int modifySwingDuration(int original) { - if (Animations.isActive() && Animations.getIgnoreHaste()) return Animations.getSpeed(); + @ModifyReturnValue(method = "getCurrentSwingDuration", at = @At("RETURN")) + private int odinaddon$overrideSwingDuration(int original) { + if (Animations.isActive() && Animations.getIgnoreHaste()) return Math.max(Animations.getSpeed(), 1); return original; } -} \ No newline at end of file +} diff --git a/src/main/java/com/odtheking/odinaddon/mixin/ModuleConfigMixin.java b/src/main/java/com/odtheking/odinaddon/mixin/ModuleConfigMixin.java new file mode 100644 index 0000000..dae9b33 --- /dev/null +++ b/src/main/java/com/odtheking/odinaddon/mixin/ModuleConfigMixin.java @@ -0,0 +1,124 @@ +package com.odtheking.odinaddon.mixin; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.JsonPrimitive; +import com.google.gson.Gson; +import com.odtheking.odin.clickgui.settings.Saving; +import com.odtheking.odin.clickgui.settings.Setting; +import com.odtheking.odin.config.ModuleConfig; +import com.odtheking.odin.features.Module; +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 java.io.File; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +/** + * Makes ModuleConfig saving "cooperative" when multiple addons accidentally (or intentionally) share the same config file. + * + * Vanilla ModuleConfig#save() overwrites the file with only the modules registered to that ModuleConfig instance. + * If multiple addons create their own ModuleConfig pointing at the same file, they will wipe each other's settings. + * + * This mixin merges the existing on-disk JSON array with the modules being saved, preserving unknown modules. + */ +@Mixin(ModuleConfig.class) +public abstract class ModuleConfigMixin { + + @Shadow @Final private HashMap modules; + @Shadow @Final private File file; + @Shadow @Final private static Gson gson; + + @Inject(method = "save", at = @At("HEAD"), cancellable = true) + private void odinaddon$mergeSave(CallbackInfo ci) { + // Only affect addon configs (config/odin/addons/*) to avoid changing Odin's own config semantics. + File parent = file.getParentFile(); + if (parent == null || !"addons".equalsIgnoreCase(parent.getName())) return; + + try { + // Read existing entries (if any) so we can preserve modules we don't own. + LinkedHashMap mergedByName = new LinkedHashMap<>(); + ArrayList existingOrder = new ArrayList<>(); + + try { + String existing = Files.readString(file.toPath()); + if (!existing.isBlank()) { + JsonElement parsed = JsonParser.parseString(existing); + if (parsed.isJsonArray()) { + for (JsonElement element : parsed.getAsJsonArray()) { + if (element == null || !element.isJsonObject()) continue; + JsonObject obj = element.getAsJsonObject(); + JsonElement nameEl = obj.get("name"); + if (nameEl == null || !nameEl.isJsonPrimitive() || !nameEl.getAsJsonPrimitive().isString()) continue; + String key = nameEl.getAsString().toLowerCase(Locale.ROOT); + // Keep first occurrence order; later duplicates will get overwritten by the merge. + if (!mergedByName.containsKey(key)) existingOrder.add(key); + mergedByName.put(key, obj); + } + } + } + } catch (Exception ignored) { + // If reading/parsing fails, fall back to writing only our modules below. + mergedByName.clear(); + existingOrder.clear(); + } + + // Overwrite/append our modules with current in-memory values. + for (Module module : modules.values()) { + JsonObject moduleObj = new JsonObject(); + moduleObj.add("name", new JsonPrimitive(module.getName())); + moduleObj.add("enabled", new JsonPrimitive(module.getEnabled())); + + JsonObject settingsObj = new JsonObject(); + for (Map.Entry> entry : module.getSettings().entrySet()) { + Setting setting = entry.getValue(); + if (setting instanceof Saving saving) { + settingsObj.add(entry.getKey(), saving.write()); + } + } + moduleObj.add("settings", settingsObj); + + String key = module.getName().toLowerCase(Locale.ROOT); + if (!mergedByName.containsKey(key)) existingOrder.add(key); + mergedByName.put(key, moduleObj); + } + + // Build the final array, keeping original ordering where possible. + JsonArray out = new JsonArray(); + Set written = new HashSet<>(); + + for (String key : existingOrder) { + JsonObject obj = mergedByName.get(key); + if (obj == null) continue; + out.add(obj); + written.add(key); + } + + // Safety: append anything not accounted for in existingOrder. + for (Map.Entry entry : mergedByName.entrySet()) { + if (written.contains(entry.getKey())) continue; + out.add(entry.getValue()); + } + + Files.writeString(file.toPath(), gson.toJson(out), StandardCharsets.UTF_8); + ci.cancel(); + } catch (Exception e) { + // Let the original save() run as a fallback. + } + } +} + diff --git a/src/main/kotlin/com/odtheking/odinaddon/OdinAnimations.kt b/src/main/kotlin/com/odtheking/odinaddon/OdinAnimations.kt index aa38d64..a172722 100644 --- a/src/main/kotlin/com/odtheking/odinaddon/OdinAnimations.kt +++ b/src/main/kotlin/com/odtheking/odinaddon/OdinAnimations.kt @@ -5,12 +5,24 @@ import com.odtheking.odin.events.core.EventBus import com.odtheking.odin.features.ModuleManager import com.odtheking.odinaddon.features.impl.render.Animations import net.fabricmc.api.ClientModInitializer +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents object OdinAnimations : ClientModInitializer { + // Shared addon config file. Multiple addons can point at the same file safely (see ModuleConfigMixin). + private val config = ModuleConfig("OdinAddon.json") override fun onInitializeClient() { listOf(this).forEach { EventBus.subscribe(it) } - ModuleManager.registerModules(ModuleConfig("OdinAddon.json"), Animations) + ModuleManager.registerModules(config, Animations) + + ClientPlayConnectionEvents.DISCONNECT.register { _, _ -> + ModuleManager.saveConfigurations() + } + + ClientLifecycleEvents.CLIENT_STOPPING.register { + ModuleManager.saveConfigurations() + } } } diff --git a/src/main/kotlin/com/odtheking/odinaddon/features/impl/render/Animations.kt b/src/main/kotlin/com/odtheking/odinaddon/features/impl/render/Animations.kt index cc7d09b..51857db 100644 --- a/src/main/kotlin/com/odtheking/odinaddon/features/impl/render/Animations.kt +++ b/src/main/kotlin/com/odtheking/odinaddon/features/impl/render/Animations.kt @@ -16,8 +16,8 @@ object Animations : Module( @JvmStatic val yaw by NumberSetting("Yaw", 0f, -180, 180, 1, desc = "Rotates your held item. Default: 0") @JvmStatic val pitch by NumberSetting("Pitch", 0f, -180, 180, 1, desc = "Rotates your held item. Default: 0") @JvmStatic val roll by NumberSetting("Roll", 0f, -180, 180, 1, desc = "Rotates your held item. Default: 0") - @JvmStatic val ignoreHaste by BooleanSetting("Ignore Effects", false, desc = "Makes the chosen speed override haste modifiers.") - @JvmStatic val speed by NumberSetting("Speed", 6, 0, 32, 1, desc = "Speed of the swing animation.").withDependency { ignoreHaste } + @JvmStatic val ignoreHaste by BooleanSetting("Ignore Effects", false, desc = "Overrides swing-speed effects like Haste/Mining Fatigue.") + @JvmStatic val speed by NumberSetting("Speed", 6, 1, 32, 1, desc = "Speed of the swing animation.").withDependency { ignoreHaste } val noEquipReset by BooleanSetting("No Equip Reset", false, desc = "Disables the equipping animation when switching items.") val noSwing by BooleanSetting("No Swing", false, desc = "Prevents your item from visually swinging forward.") @@ -30,4 +30,4 @@ object Animations : Module( @JvmStatic val isActive get() = enabled -} \ No newline at end of file +} diff --git a/src/main/resources/odinaddon.mixins.json b/src/main/resources/odinaddon.mixins.json index d9a6b72..7bc99b2 100644 --- a/src/main/resources/odinaddon.mixins.json +++ b/src/main/resources/odinaddon.mixins.json @@ -4,7 +4,8 @@ "package": "com.odtheking.odinaddon.mixin", "compatibilityLevel": "JAVA_21", "mixins": [ - "LivingEntityMixin" + "LivingEntityMixin", + "ModuleConfigMixin" ], "injectors": { "defaultRequire": 1