using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Newtonsoft.Json;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;
using UnityEngine.Serialization;
using VRC;
using VRC.SDK3.Avatars.Components;
using VRC.SDK3.Avatars.ScriptableObjects;
using VRC.SDKBase;
using YamlDotNet.Core.Tokens;
public class DeckImporter : EditorWindow
{
[SerializeField] private TextAsset deckJson;
[SerializeField] private VRCExpressionsMenu menuRoot;
[SerializeField] private AnimatorController outputController;
[MenuItem("Window/Deck Importer")]
static void ShowWindow()
{
GetWindow<DeckImporter>().Show();
}
private void OnGUI()
{
var so = new SerializedObject(this);
EditorGUILayout.PropertyField(so.FindProperty(nameof(deckJson)));
EditorGUILayout.PropertyField(so.FindProperty(nameof(menuRoot)));
EditorGUILayout.PropertyField(so.FindProperty(nameof(outputController)));
if (so.hasModifiedProperties)
so.ApplyModifiedProperties();
if (GUILayout.Button("Generate"))
{
Generate();
}
}
void Generate()
{
var json = deckJson.text;
var deck = JsonConvert.DeserializeObject<Deck>(json);
var controller = new AnimatorController();
var targetPath = AssetDatabase.GetAssetPath(outputController);
foreach (var obj in AssetDatabase.LoadAllAssetsAtPath(targetPath))
{
if (AssetDatabase.IsSubAsset(obj))
{
Debug.Log("Removing: " + obj);
AssetDatabase.RemoveObjectFromAsset(obj);
}
}
var displayStateMachine = new AnimatorStateMachine
{
name = "Display"
};
var displayLayer = new AnimatorControllerLayer
{
name = "Display",
stateMachine = displayStateMachine,
defaultWeight = 1f
};
AssetDatabase.AddObjectToAsset(displayStateMachine, outputController);
controller.AddLayer(displayLayer);
var drawStateMachine = new AnimatorStateMachine
{
name = "Draw"
};
var drawLayer = new AnimatorControllerLayer
{
name = "Draw",
stateMachine = drawStateMachine,
defaultWeight = 1f
};
AssetDatabase.AddObjectToAsset(drawStateMachine, outputController);
controller.AddLayer(drawLayer);
var drawIdleState = drawStateMachine.AddState("Idle", 250 * Vector3.left);
var delayClip = new AnimationClip();
delayClip.name = "Delay - 0.1 seconds";
delayClip.SetCurve("Fake Object", typeof(GameObject), "activeSelf", AnimationCurve.Constant(0f, 0.1f, 0f));
AssetDatabase.AddObjectToAsset(delayClip, outputController);
controller.AddParameter("Control/Selected Card", AnimatorControllerParameterType.Int);
controller.AddParameter("Control/Draw", AnimatorControllerParameterType.Int);
controller.AddParameter("Internal/RNG", AnimatorControllerParameterType.Int);
int id = 0;
Dictionary<string, List<Card>> sets = new();
Dictionary<Card, int> cardIDMap = new();
Dictionary<string, int> setIDMap = new();
Dictionary<Card, AnimatorState> cardStateMap = new();
foreach (var card in deck.cards)
{
Debug.Log(card);
var cardState = displayLayer.stateMachine.AddState(card.name);
var cardClip = new AnimationClip();
cardClip.name = "Display - " + card.name;
var cardUVXCurve = AnimationCurve.Constant(0f, 1f, card.uv.x);
var cardUVYCurve = AnimationCurve.Constant(0f, 1f, card.uv.y);
var cardUVZCurve = AnimationCurve.Constant(0f, 1f, card.uv.z);
var cardUVWCurve = AnimationCurve.Constant(0f, 1f, card.uv.w);
cardClip.SetCurve("Pivot Offset/Card", typeof(MeshRenderer), "material._MainTex_ST.x", cardUVXCurve);
cardClip.SetCurve("Pivot Offset/Card", typeof(MeshRenderer), "material._MainTex_ST.y", cardUVYCurve);
cardClip.SetCurve("Pivot Offset/Card", typeof(MeshRenderer), "material._MainTex_ST.z", cardUVZCurve);
cardClip.SetCurve("Pivot Offset/Card", typeof(MeshRenderer), "material._MainTex_ST.w", cardUVWCurve);
cardState.motion = cardClip;
AssetDatabase.AddObjectToAsset(cardClip, outputController);
var cardTransition = displayStateMachine.AddAnyStateTransition(cardState);
cardTransition.AddCondition(AnimatorConditionMode.Equals, id, "Control/Selected Card");
cardTransition.hasExitTime = false;
cardTransition.duration = 0f;
cardTransition.canTransitionToSelf = false;
foreach (var tag in card.tags)
{
if (!sets.TryGetValue(tag, out var list))
{
list = new();
sets[tag] = list;
}
list.Add(card);
}
var setCardState = drawStateMachine.AddState(card.name, 250 * Vector3.right + 50 * id * Vector3.down);
setCardState.motion = delayClip;
var returnTransition = setCardState.AddExitTransition();
returnTransition.hasExitTime = true;
returnTransition.exitTime = 1f;
returnTransition.duration = 0f;
var setCardDriver =
setCardState.AddStateMachineBehaviour<VRCAvatarParameterDriver>();
setCardDriver.parameters.Add(new VRC_AvatarParameterDriver.Parameter
{
name = "Control/Selected Card",
type = VRC_AvatarParameterDriver.ChangeType.Set,
value = id
});
setCardDriver.MarkDirty();
cardStateMap[card] = setCardState;
cardIDMap[card] = id;
++id;
}
int setID = 0;
foreach (var (tag, set) in sets)
{
var setState = drawStateMachine.AddState(tag, 50 * setID * Vector3.down);
setState.motion = delayClip;
var setTransition = drawIdleState.AddTransition(setState);
setTransition.hasExitTime = false;
setTransition.duration = 0;
setTransition.AddCondition(AnimatorConditionMode.Equals, setID + 1, "Control/Draw");
var setStateDriver =
setState.AddStateMachineBehaviour<VRCAvatarParameterDriver>();
setStateDriver.localOnly = true;
setStateDriver.parameters.Add(new VRC_AvatarParameterDriver.Parameter()
{
name = "Control/Draw",
type = VRC_AvatarParameterDriver.ChangeType.Set,
value = 0
});
setStateDriver.parameters.Add(new VRC_AvatarParameterDriver.Parameter()
{
name = "Internal/RNG",
type = VRC_AvatarParameterDriver.ChangeType.Random,
valueMin = 0,
valueMax = set.Count - 1,
});
setStateDriver.MarkDirty();
int randomID = 0;
foreach (var card in set)
{
var setCardState = cardStateMap[card];
var enterTransition = setState.AddTransition(setCardState);
enterTransition.hasExitTime = true;
enterTransition.exitTime = 1;
enterTransition.duration = 0;
enterTransition.AddCondition(AnimatorConditionMode.Equals, randomID, "Internal/RNG");
++randomID;
}
setIDMap[tag] = setID;
++setID;
}
string oldName = outputController.name;
EditorUtility.CopySerialized(controller, outputController);
outputController.name = oldName;
AssetDatabase.SaveAssetIfDirty(outputController);
var menuPath = AssetDatabase.GetAssetPath(menuRoot);
foreach (var obj in AssetDatabase.LoadAllAssetsAtPath(menuPath))
{
if (AssetDatabase.IsSubAsset(obj))
{
Debug.Log("Removing: " + obj);
AssetDatabase.RemoveObjectFromAsset(obj);
}
}
VRCExpressionsMenu menu = CreateInstance<VRCExpressionsMenu>();
menu.Parameters = menuRoot.Parameters;
VRCExpressionsMenu selectMenu = CreateInstance<VRCExpressionsMenu>();
selectMenu.name = "Select Card";
selectMenu.Parameters = menuRoot.Parameters;
menu.controls.Add(new VRCExpressionsMenu.Control
{
name = "Select Card",
type = VRCExpressionsMenu.Control.ControlType.SubMenu,
subMenu = selectMenu
});
AssetDatabase.AddObjectToAsset(selectMenu, menuRoot);
foreach (var (key, set) in sets)
{
List<Card> sorted = set.OrderBy(card => card.name).ToList();
VRCExpressionsMenu selectSetMenu = CreateInstance<VRCExpressionsMenu>();
selectSetMenu.name = "Set - " + key;
selectSetMenu.Parameters = menuRoot.Parameters;
selectMenu.controls.Add(new VRCExpressionsMenu.Control
{
name = key,
type = VRCExpressionsMenu.Control.ControlType.SubMenu,
subMenu = selectSetMenu
});
AssetDatabase.AddObjectToAsset(selectSetMenu, menuRoot);
for (int i = 0; i < sorted.Count; i += 8)
{
int amount = Mathf.Min(sorted.Count, i + 8) - i;
var range = sorted.GetRange(i, amount);
VRCExpressionsMenu cardMenu = CreateInstance<VRCExpressionsMenu>();
cardMenu.name = range[0].name + "-" + range[^1].name;
cardMenu.Parameters = menuRoot.Parameters;
foreach (var card in sorted.GetRange(i, amount))
{
cardMenu.controls.Add(new VRCExpressionsMenu.Control
{
name = card.name,
type = VRCExpressionsMenu.Control.ControlType.Toggle,
parameter = new VRCExpressionsMenu.Control.Parameter
{
name = "Control/Selected Card"
},
value = cardIDMap[card]
});
}
selectSetMenu.controls.Add(new VRCExpressionsMenu.Control
{
name = range[0].name + "-" + range[^1].name,
type = VRCExpressionsMenu.Control.ControlType.SubMenu,
subMenu = cardMenu
});
AssetDatabase.AddObjectToAsset(cardMenu, menuRoot);
}
}
VRCExpressionsMenu drawMenu = CreateInstance<VRCExpressionsMenu>();
drawMenu.name = "Draw Card";
drawMenu.Parameters = menuRoot.Parameters;
menu.controls.Add(new VRCExpressionsMenu.Control
{
name = "Draw Card",
type = VRCExpressionsMenu.Control.ControlType.SubMenu,
subMenu = drawMenu
});
AssetDatabase.AddObjectToAsset(drawMenu, menuRoot);
foreach (var (tag, set) in sets)
{
drawMenu.controls.Add(new VRCExpressionsMenu.Control
{
name = tag,
type = VRCExpressionsMenu.Control.ControlType.Toggle,
parameter = new VRCExpressionsMenu.Control.Parameter()
{
name = "Control/Draw"
},
value = setIDMap[tag] + 1
});
}
oldName = menuRoot.name;
EditorUtility.CopySerialized(menu, menuRoot);
menuRoot.name = oldName;
AssetDatabase.SaveAssetIfDirty(menuRoot);
}
}