using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using KKAPI.Chara; using KKAPI.Studio.SaveLoad; using KKAPI.Studio.UI; using Studio; using UniRx; using UnityEngine; using UnityEngine.SceneManagement; namespace KKAPI.Studio { /// /// Provides a way to add custom menu items to CharaStudio, and gives useful methods for interfacing with the studio. /// public static partial class StudioAPI { private static readonly List _customCurrentStateCategories = new List(); private static GameObject _customStateRoot; /// /// Add a new custom category to the Anim > CurrentState tab in the studio top-left menu. /// Can use this at any point. /// [Obsolete("Use GetOrCreateCurrentStateCategory instead")] public static void CreateCurrentStateCategory(CurrentStateCategory category) { if (category == null) throw new ArgumentNullException(nameof(category)); if (!InsideStudio) { KoikatuAPI.Logger.LogDebug("Tried to run StudioAPI.CreateCurrentStateCategory outside of studio!"); return; } if (StudioLoaded) CreateCategory(category); _customCurrentStateCategories.Add(category); } /// /// Add a new custom category to the Anim > CurrentState tab in the studio top-left menu. /// Can use this at any point. Always returns null outside of studio. /// If the name is empty or null, the Misc/Other category is returned. /// public static CurrentStateCategory GetOrCreateCurrentStateCategory(string name) { if (!InsideStudio) { KoikatuAPI.Logger.LogDebug("Tried to run StudioAPI.CreateCurrentStateCategory outside of studio!"); return null; } if (string.IsNullOrEmpty(name)) name = "Misc/Other"; var existing = _customCurrentStateCategories.FirstOrDefault(x => x.CategoryName == name); if (existing != null) return existing; var newCategory = new CurrentStateCategory(name); if (StudioLoaded) CreateCategory(newCategory); _customCurrentStateCategories.Add(newCategory); return newCategory; } /// /// Get all instances of this controller that belong to characters that are selected in Studio's Workspace. /// public static IEnumerable GetSelectedControllers() where T : CharaCustomFunctionController { return GetSelectedCharacters().Select(x => x.charInfo?.GetComponent()).Where(x => x != null); } /// /// Get all character objects currently selected in Studio's Workspace. /// public static IEnumerable GetSelectedCharacters() { return GetSelectedObjects().OfType(); } /// /// Get all objects (all types) currently selected in Studio's Workspace. /// public static IEnumerable GetSelectedObjects() { if (!StudioLoaded) return Enumerable.Empty(); return GuideObjectManager.Instance.selectObjectKey.Select(global::Studio.Studio.GetCtrlInfo).Where(x => x != null); } private static void CreateCategory(CurrentStateCategory category) { if (category == null) throw new ArgumentNullException(nameof(category)); if (_customStateRoot == null) _customStateRoot = GameObject.Find("StudioScene/Canvas Main Menu/02_Manipulate/00_Chara/01_State/Viewport/Content"); category.CreateCategory(_customStateRoot); } internal static void Init(bool insideStudio) { InsideStudio = insideStudio; if (!insideStudio) return; Hooks.SetupHooks(); StudioSaveLoadApi.Init(); SceneManager.sceneLoaded += SceneLoaded; DebugControls(); } /// /// True if we are currently inside CharaStudio.exe /// public static bool InsideStudio { get; private set; } /// /// True inside studio after it finishes loading the interface (when the starting loading screen finishes), /// right before custom controls are created. /// public static bool StudioLoaded { get; private set; } /// /// Fires once after studio finished loading the interface, right before custom controls are created. /// public static event EventHandler StudioLoadedChanged; private static void SceneLoaded(Scene scene, LoadSceneMode loadSceneMode) { if (!StudioLoaded && scene.name == "Studio") { StudioLoaded = true; if (StudioLoadedChanged != null) { foreach (var callback in StudioLoadedChanged.GetInvocationList()) { try { ((EventHandler) callback)(KoikatuAPI.Instance, EventArgs.Empty); } catch (Exception e) { KoikatuAPI.Logger.LogError(e); } } } foreach (var cat in _customCurrentStateCategories) CreateCategory(cat); } } [Conditional("DEBUG")] private static void DebugControls() { var cat = GetOrCreateCurrentStateCategory("Control test category"); cat.AddControl( new CurrentStateCategoryToggle( "Test 1", 2, c => { KoikatuAPI.Logger.LogMessage(c?.charInfo?.name + " - updateValue"); return 1; })) .Value.Subscribe(val => KoikatuAPI.Logger.LogMessage(val)); cat.AddControl(new CurrentStateCategoryToggle("Test 2", 3, c => 2)).Value.Subscribe(val => KoikatuAPI.Logger.LogMessage(val)); cat.AddControl(new CurrentStateCategoryToggle("Test 3", 4, c => 3)).Value.Subscribe(val => KoikatuAPI.Logger.LogMessage(val)); var cat2 = GetOrCreateCurrentStateCategory("Control test category"); cat2.AddControl(new CurrentStateCategorySwitch("Test add", c => true)).Value.Subscribe(val => KoikatuAPI.Logger.LogMessage(val)); cat2.AddControl(new CurrentStateCategorySlider("Test slider", c => 0.75f)).Value.Subscribe(val => KoikatuAPI.Logger.LogMessage(val)); cat2.AddControl(new CurrentStateCategoryDropdown("dropdown test", new[] {"item 1", "i2", "test 3"}, c => 1)).Value.Subscribe(val => KoikatuAPI.Logger.LogMessage("dd " + val)); } } }