using System.Collections.Generic; using System.Linq; using gay.lilyy.aaccore; using gay.lilyy.aacshared.runtimecomponents; using UnityEditor; using UnityEngine; namespace gay.lilyy.aacshared.layers { [InitializeOnLoad] public class Floater : LayerGroup { // remove when ready for uploaded public override bool experimental => false; public override bool shouldWarnIfNotApplicable => false; private static readonly Floater _instance = new(); static Floater() {} public override string DisplayName => "Floater"; public override string SystemName => "floater"; private static FloaterDefinition[] getDefinitions(AACAssets assets) { return assets.ctx.AvatarRootObject.GetComponentsInChildren(); } public override bool IsApplicable(AACAssets assets) { return getDefinitions(assets).Length > 0; } private void createLayer(AACAssets assets, MultiTransformDefinition multiDef) { var layer = assets.fx.NewLayer($"Floater {multiDef.Definition.name} and {multiDef.Transforms.Count - 1} more"); if (!new[] { "x", "y", "z" }.Contains(multiDef.Definition.spinAxis.ToLower())) { Logger.LogWarning($"Floater system: Invalid spin axis '{multiDef.Definition.spinAxis}' in definition '{multiDef.Definition.name}'. Must be one of 'x', 'y', or 'z'. Skipping."); return; } var state = layer.NewState("Floater").WithAnimation(assets.aac.NewClip().Animating(action => { Logger.LogInfo($"Floater system: Creating animation for definition '{multiDef.Definition.name}' with {multiDef.Transforms.Count} transforms"); foreach (var t in multiDef.Transforms) { if (multiDef.Definition.floatHeight != 0f) { action.Animates(t, "m_LocalPosition.y").WithSecondsUnit(unit => { unit .Easing(0, t.transform.localPosition.y); unit .Easing(multiDef.Definition.loopInSeconds / 2f, t.transform.localPosition.y + multiDef.Definition.floatHeight) .Easing(multiDef.Definition.loopInSeconds, t.transform.localPosition.y); }); } if (multiDef.Definition.spinCycles > 0) { string axis = multiDef.Definition.spinAxis.ToLower(); string prop = $"m_LocalEulerAngles.{axis}"; float segment = multiDef.Definition.loopInSeconds / multiDef.Definition.spinCycles; for (int cycle = 0; cycle < multiDef.Definition.spinCycles; cycle++) { action.Animates(t, prop).WithSecondsUnit(unit => { float baseAngle = 0f; // ignore quaternion components entirely float startTime = cycle * segment; float endTime = (cycle + 1) * segment; float startAngle = baseAngle + 360f * cycle; float endAngle = baseAngle + 360f * (cycle + 1); Logger.LogDebug($"Floater system: Animating {t.name} {prop} on cycle {cycle} from {startAngle} to {endAngle} between {startTime}s and {endTime}s"); unit .Linear(startTime, startAngle) .Linear(endTime, endAngle); Logger.LogDebug($"unit.Linear({startTime}, {startAngle}).Linear({endTime}, {endAngle})"); }); } // Fill OTHER Euler axes with constant values foreach (var other in new[] { "x", "y", "z" }) { if (other == axis) continue; float current = other == "x" ? t.transform.localEulerAngles.x : other == "y" ? t.transform.localEulerAngles.y : t.transform.localEulerAngles.z; Logger.LogDebug($"Floater system: Setting constant {t.name} {other} to {current}"); action.Animates(t, $"m_LocalEulerAngles.{other}") .WithSecondsUnit(unit => { unit .Linear(0f, current) .Linear(multiDef.Definition.loopInSeconds, current); }); } } } }).Looping()); if (multiDef.Definition.toggleParamName != "") { var param = layer.BoolParameter(multiDef.Definition.toggleParamName); var offState = layer.NewState("Floater Off").WithAnimation(assets.emptyClip); layer.WithDefaultState(offState); offState.TransitionsTo(state).When(param.IsTrue()); state.TransitionsTo(offState).When(param.IsFalse()); } } private struct MultiTransformDefinition { public List Transforms { get; set; } public FloaterDefinition Definition { get; set; } } public override void Run(AACAssets assets) { var definitions = getDefinitions(assets); Logger.LogDebug($"Floater system: Found {definitions.Length} floater definitions"); var matchedDefinitions = new Dictionary(); foreach (var def in definitions) { if (!def.enabled) continue; if (!def.enableOnPC && assets.isPC) continue; if (!def.enableOnMobile && !assets.isPC) continue; Logger.LogInfo($"Floater system: Found definition '{def.name}'"); var hash = def.GetHashCode(); if (!matchedDefinitions.ContainsKey(hash)) { matchedDefinitions[hash] = new MultiTransformDefinition { Definition = def, Transforms = new List() }; } else { Logger.LogInfo($"Floater system: Adding additional transform to definition '{def.name}'"); } matchedDefinitions[hash].Transforms.Add(def.transform); } foreach (var multiDef in matchedDefinitions.Values) { createLayer(assets, multiDef); } } } }