internal static void Init() { DetectMoreAccessories(); HarmonyPatcher.PatchAll(typeof(Hooks)); MakerAPI.InsideMakerChanged += MakerAPI_InsideMakerChanged; MakerAPI.MakerFinishedLoading += (sender, args) => OnSelectedMakerSlotChanged(sender, 0); if (MoreAccessoriesInstalled) { var getAccCmpM = AccessTools.Method(_moreAccessoriesType, "GetChaAccessoryComponent"); _getChaAccessoryCmp = (control, componentIndex) => (ChaAccessoryComponent)getAccCmpM.Invoke(_moreAccessoriesInstance, new object[] { control, componentIndex }); var getAccCmpIndexM = AccessTools.Method(_moreAccessoriesType, "GetChaAccessoryComponentIndex"); _getChaAccessoryCmpIndex = (control, component) => (int)getAccCmpIndexM.Invoke(_moreAccessoriesInstance, new object[] { control, component }); } else { _getChaAccessoryCmp = (control, i) => control.cusAcsCmp[i]; _getChaAccessoryCmpIndex = (control, component) => Array.IndexOf(control.cusAcsCmp, component); } if (KoikatuAPI.EnableDebugLogging) { SelectedMakerAccSlotChanged += (sender, args) => KoikatuAPI.Log(LogLevel.Message, $"SelectedMakerAccSlotChanged - id: {args.SlotIndex}, cvs: {args.CvsAccessory.transform.name}, component: {args.AccessoryComponent?.name ?? "null"}"); #if KK AccessoriesCopied += (sender, args) => KoikatuAPI.Log(LogLevel.Message, $"AccessoriesCopied - ids: {string.Join(", ", args.CopiedSlotIndexes.Select(x => x.ToString()).ToArray())}, src:{args.CopySource}, dst:{args.CopyDestination}"); #endif AccessoryTransferred += (sender, args) => KoikatuAPI.Log(LogLevel.Message, $"AccessoryTransferred - srcId:{args.SourceSlotIndex}, dstId:{args.DestinationSlotIndex}"); } }
public static Transform AddNewSubCategory(UI_ToggleGroupCtrl mainCategory, MakerCategory subCategory) { KoikatuAPI.Log(LogLevel.Debug, $"[MakerAPI] Adding custom subcategory {subCategory.SubCategoryName} to {mainCategory.transform.name}"); var tr = Object.Instantiate(SubCategoryCopy.gameObject, mainCategory.transform, true).transform; tr.name = subCategory.SubCategoryName; var trTop = tr.Find("CustomSubcategoryTop"); trTop.name = subCategory.SubCategoryName + "Top"; foreach (Transform oldObj in trTop.transform) { Object.Destroy(oldObj.gameObject); } tr.GetComponentInChildren <TextMeshProUGUI>().text = GetSubcategoryDisplayName(subCategory); var tgl = tr.GetComponent <Toggle>(); tgl.group = mainCategory.transform.GetComponent <ToggleGroup>(); var cgroup = trTop.GetComponent <CanvasGroup>(); mainCategory.items = mainCategory.items.AddToArray(new UI_ToggleGroupCtrl.ItemInfo { tglItem = tgl, cgItem = cgroup }); KoikatuAPI.Instance.StartCoroutine(FinishInit(trTop)); tr.gameObject.SetActive(true); return(tr); }
private static void DetectMoreAccessories() { try { _moreAccessoriesType = Type.GetType("MoreAccessoriesKOI.MoreAccessories, MoreAccessories, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"); if (_moreAccessoriesType != null) { _moreAccessoriesInstance = Object.FindObjectOfType(_moreAccessoriesType); var slotAddEvent = _moreAccessoriesType.GetEvent("onCharaMakerSlotAdded", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (slotAddEvent != null) { slotAddEvent.AddEventHandler(_moreAccessoriesInstance, new Action <int, Transform>((i, transform) => OnMakerAccSlotAdded(_moreAccessoriesInstance, i, transform))); } else { _moreAccessoriesType = null; KoikatuAPI.Log(LogLevel.Message | LogLevel.Error, "[KKAPI] WARNING: Your MoreAccesories is outdated! Some features won't work correctly until you update to the latest version."); } } } catch (Exception e) { _moreAccessoriesType = null; KoikatuAPI.Log(LogLevel.Warning, "Failed to detect MoreAccessories!"); KoikatuAPI.Log(LogLevel.Debug, e); } }
private static void OnMakerBaseLoaded() { if (MakerBaseLoaded != null) { var args = new RegisterCustomControlsEvent(); foreach (var handler in MakerBaseLoaded.GetInvocationList()) { try { ((EventHandler <RegisterCustomControlsEvent>)handler).Invoke(KoikatuAPI.Instance, args); } catch (Exception e) { KoikatuAPI.Log(LogLevel.Error, e); } } } DebugControls(); foreach (var baseGuiEntry in _guiEntries) { baseGuiEntry.Initialize(); } }
private static void OnSelectedMakerSlotChanged(object source, int newSlotIndex) { if (newSlotIndex == SelectedMakerAccSlot) { return; } SelectedMakerAccSlot = newSlotIndex; if (KoikatuAPI.EnableDebugLogging) { KoikatuAPI.Log(LogLevel.Message, "SelectedMakerSlotChanged - slot:" + newSlotIndex); } if (SelectedMakerAccSlotChanged == null) { return; } try { SelectedMakerAccSlotChanged(source, new AccessorySlotEventArgs(newSlotIndex)); } catch (Exception ex) { KoikatuAPI.Log(LogLevel.Error, "Subscription to SelectedMakerSlot crashed: " + ex); } }
private static void OnCopyAcs(CvsAccessoryCopy instance) { if (AccessoriesCopied == null) { return; } try { var selected = instance.GetComponentsInChildren <UnityEngine.UI.Toggle>() .Where(x => x.isOn) .Select(x => x.transform.parent.name) .Select(n => int.Parse(n.Substring("kind".Length))) .ToList(); var dropdowns = Traverse.Create(instance).Field("ddCoordeType").GetValue <TMP_Dropdown[]>(); var args = new AccessoryCopyEventArgs(selected, (ChaFileDefine.CoordinateType)dropdowns[1].value, (ChaFileDefine.CoordinateType)dropdowns[0].value); AccessoriesCopied(instance, args); } catch (Exception ex) { KoikatuAPI.Log(LogLevel.Error, "Crash in AccessoriesCopied event: " + ex); } }
private static void CreateCustomControls() { // Craete controls in tabs foreach (Transform categoryTransfrom in GameObject.Find("CvsMenuTree").transform) { CreateCustomControlsInCategory(categoryTransfrom); } // Create sidebar controls if (_sidebarEntries.Any()) { var sidebarTop = GameObject.Find("CustomScene/CustomRoot/FrontUIGroup/CvsDraw/Top").transform; var sep = new SidebarSeparator(KoikatuAPI.Instance); sep.CreateControl(sidebarTop); foreach (var sidebarEntry in _sidebarEntries) { sidebarEntry.CreateControl(sidebarTop); } KoikatuAPI.Log(LogLevel.Debug, $"[MakerAPI] Added {_sidebarEntries.Count} custom controls " + "to Control Panel sidebar"); } if (_accessoryWindowEntries.Any()) { CreateCustomAccessoryWindowControls(); } }
private static void CreateCustomControlsInSubCategory(Transform subCategoryTransform, ICollection <BaseGuiEntry> entriesToAdd) { if (entriesToAdd.Count == 0) { return; } var contentParent = FindSubcategoryContentParent(subCategoryTransform); BaseUnityPlugin lastOwner = contentParent.childCount > 1 ? KoikatuAPI.Instance : null; foreach (var customControl in entriesToAdd) { if (lastOwner != customControl.Owner && lastOwner != null) { new MakerSeparator(new MakerCategory(null, null), KoikatuAPI.Instance).CreateControl(contentParent); } customControl.CreateControl(contentParent); lastOwner = customControl.Owner; } var category = entriesToAdd.First().Category; KoikatuAPI.Log(LogLevel.Debug, $"[MakerAPI] Added {entriesToAdd.Count()} custom controls " + $"to {category.CategoryName}/{category.SubCategoryName}"); }
/// <summary> /// Needs to run before UI_ToggleGroupCtrl.Start of the category runs, or it won't get added properly /// </summary> private static void AddMissingSubCategories(UI_ToggleGroupCtrl mainCategory) { var categoryTransfrom = mainCategory.transform; // Can break stuff, 06_SystemTop might be fine but needs testing if (categoryTransfrom.name == "04_AccessoryTop" || categoryTransfrom.name == "06_SystemTop") { return; } // Sorting breaks hair selector layout, too lazy to fix var noSorting = categoryTransfrom.name == "02_HairTop"; var transformsToSort = new List <UniRx.Tuple <Transform, int> >(); foreach (var category in _categories) { if (categoryTransfrom.name != category.CategoryName) { continue; } var categorySubTransform = categoryTransfrom.Find(category.SubCategoryName) ?? SubCategoryCreator.AddNewSubCategory(mainCategory, category); transformsToSort.Add(new UniRx.Tuple <Transform, int>(categorySubTransform, category.Position)); } if (noSorting) { return; } foreach (Transform subTransform in categoryTransfrom) { if (transformsToSort.Any(x => x.Item1 == subTransform)) { continue; } var builtInCategory = MakerConstants.GetBuiltInCategory(categoryTransfrom.name, subTransform.name); if (builtInCategory != null) { transformsToSort.Add(new UniRx.Tuple <Transform, int>(subTransform, builtInCategory.Position)); } else { KoikatuAPI.Log(LogLevel.Warning, $"[MakerAPI] Missing MakerCategory for existing transfrom {categoryTransfrom.name} / {subTransform.name}"); } } var index = 0; foreach (var tuple in transformsToSort.OrderBy(x => x.Item2)) { tuple.Item1.SetSiblingIndex(index++); } KoikatuAPI.Instance.StartCoroutine(FixCategoryContentOffsets(mainCategory)); }
internal void OnCoordinateBeingSavedInternal(ChaFileCoordinate coordinate) { try { OnCoordinateBeingSaved(coordinate); } catch (Exception e) { KoikatuAPI.Log(LogLevel.Error, e); } }
/// <summary> /// Add custom sub categories. They need to be added before maker starts loading, /// or in the <see cref="RegisterCustomSubCategories"/> event. /// </summary> internal static void AddSubCategory(MakerCategory category) { if (!_categories.Contains(category)) { _categories.Add(category); } else { KoikatuAPI.Log(LogLevel.Info, $"[MakerAPI] Duplicate custom subcategory was added: {category} The duplicate will be ignored."); } }
public static void ChaControl_InitializePostHook(ChaControl __instance) { KoikatuAPI.Log(LogLevel.Debug, $"[KKAPI] Character card load: {GetLogName(__instance)} {(MakerAPI.CharaListIsLoading ? "inside CharaList" : string.Empty)}"); ChaControls.Add(__instance); if (!MakerAPI.CharaListIsLoading) { CreateOrAddBehaviours(__instance); } }
internal static void Init() { HarmonyPatcher.PatchAll(typeof(Hooks)); // Cards ------------------------- ExtendedSave.CardBeingSaved += OnCardBeingSaved; MakerAPI.ChaFileLoaded += (sender, args) => { var chaControl = MakerAPI.GetCharacterControl(); if (chaControl != null) { ReloadChara(chaControl); } }; // Coordinates ------------------- ExtendedSave.CoordinateBeingSaved += file => { if (file == null) { return; } // Safe to assume we're in maker var character = MakerAPI.GetCharacterControl(); if (character == null) { KoikatuAPI.Log(LogLevel.Error, "[KKAPI] OnCoordinateBeingSaved fired outside chara maker for " + file.coordinateName); KoikatuAPI.Log(LogLevel.Info, new StackTrace()); return; } OnCoordinateBeingSaved(character, file); }; ExtendedSave.CoordinateBeingLoaded += file => { if (Hooks.ClothesFileControlLoading || file == null) { return; } // Coord cards are loaded by loading into the character's nowCoordinate var cf = ChaControls.FirstOrDefault(x => x.nowCoordinate == file); if (cf != null) { OnCoordinateBeingLoaded(cf, file); } }; if (KoikatuAPI.EnableDebugLogging) { RegisterExtraBehaviour <TestCharaCustomFunctionController>(null); } }
internal void OnCardBeingSavedInternal(GameMode gamemode) { try { OnCardBeingSaved(gamemode); } catch (Exception e) { KoikatuAPI.Log(LogLevel.Error, e); } }
private static void OnCardBeingSaved(ChaFile chaFile) { KoikatuAPI.Log(LogLevel.Debug, "[KKAPI] Character save: " + chaFile.parameter.fullname); var gamemode = KoikatuAPI.GetCurrentGameMode(); foreach (var behaviour in GetBehaviours(MakerAPI.GetCharacterControl())) { behaviour.OnCardBeingSavedInternal(gamemode); } }
internal void OnCoordinateBeingLoadedInternal(ChaFileCoordinate coordinate) { try { if (!ControllerRegistration.MaintainCoordinateState) { OnCoordinateBeingLoaded(coordinate); } OnCoordinateBeingLoaded(coordinate, ControllerRegistration.MaintainCoordinateState); } catch (Exception e) { KoikatuAPI.Log(LogLevel.Error, e); } }
internal void OnReloadInternal(GameMode currentGameMode) { try { if (!ControllerRegistration.MaintainState) { OnReload(currentGameMode); } OnReload(currentGameMode, ControllerRegistration.MaintainState); } catch (Exception e) { KoikatuAPI.Log(LogLevel.Error, e); } }
private static void OnChaFileLoaded(ChaFileLoadedEventArgs chaFileLoadedEventArgs) { if (ChaFileLoaded != null) { foreach (var handler in ChaFileLoaded.GetInvocationList()) { try { ((EventHandler <ChaFileLoadedEventArgs>)handler).Invoke(KoikatuAPI.Instance, chaFileLoadedEventArgs); } catch (Exception e) { KoikatuAPI.Log(LogLevel.Error, e); } } } }
private static void DebugControls() { var instance = KoikatuAPI.Instance; var cat = MakerConstants.Face.All; AddControl(new MakerSeparator(cat, instance)); AddControl(new MakerSeparator(cat, instance)); AddControl(new MakerSeparator(cat, instance)); AddControl(new MakerToggle(cat, "test toggle", instance)) .ValueChanged.Subscribe(b => KoikatuAPI.Log(LogLevel.Message, b)); AddControl(new MakerButton("test btn", cat, instance)) .OnClick.AddListener(() => KoikatuAPI.Log(LogLevel.Message, "Clicked")); AddControl(new MakerColor("test color", true, cat, Color.magenta, instance)) .ValueChanged.Subscribe(color => KoikatuAPI.Log(LogLevel.Message, color)); AddControl(new MakerDropdown("test toggle", new[] { "t0", "t1", "t2", "t3" }, cat, 1, instance)) .ValueChanged.Subscribe(b => KoikatuAPI.Log(LogLevel.Message, b)); AddControl(new MakerRadioButtons(cat, instance, "radio btns", "b1", "b2")) .ValueChanged.Subscribe(b => KoikatuAPI.Log(LogLevel.Message, b)); AddControl(new MakerSlider(cat, "test slider", 0, 1, 1, instance)) .ValueChanged.Subscribe(b => KoikatuAPI.Log(LogLevel.Message, b)); AddControl(new MakerText("test text test text test text test text test text test " + "text test text test text test text test text", cat, instance)); MakerLoadToggle.AddLoadToggle(new MakerLoadToggle("Test toggle")) .ValueChanged.Subscribe(b => KoikatuAPI.Log(LogLevel.Message, b)); MakerLoadToggle.AddLoadToggle(new MakerLoadToggle("Test toggle 2")) .ValueChanged.Subscribe(b => KoikatuAPI.Log(LogLevel.Message, b)); MakerCoordinateLoadToggle.AddLoadToggle(new MakerCoordinateLoadToggle("Test toggle")) .ValueChanged.Subscribe(b => KoikatuAPI.Log(LogLevel.Message, b)); MakerCoordinateLoadToggle.AddLoadToggle(new MakerCoordinateLoadToggle("Test toggle 2")) .ValueChanged.Subscribe(b => KoikatuAPI.Log(LogLevel.Message, b)); AddSidebarControl(new SidebarToggle("Test toggle", false, instance)) .ValueChanged.Subscribe(b => KoikatuAPI.Log(LogLevel.Message, b)); AddSidebarControl(new SidebarToggle("Test toggle2", true, instance)) .ValueChanged.Subscribe(b => KoikatuAPI.Log(LogLevel.Message, b)); AddAccessoryWindowControl(new MakerToggle(cat, "test toggle", null)) .ValueChanged.Subscribe(b => KoikatuAPI.Log(LogLevel.Message, $"Toggled to {b} in accessory slot index {AccessoriesApi.SelectedMakerAccSlot}")); AddAccessoryWindowControl(new MakerColor("test accessory color", false, cat, Color.cyan, instance) { ColorBoxWidth = 230 }) .ValueChanged.Subscribe(b => KoikatuAPI.Log(LogLevel.Message, $"Color to {b} in accessory slot index {AccessoriesApi.SelectedMakerAccSlot}")); }
private static void OnMakerStartedLoading() { if (MakerStartedLoading != null) { var args = new RegisterCustomControlsEvent(); foreach (var handler in MakerStartedLoading.GetInvocationList()) { try { ((EventHandler <RegisterCustomControlsEvent>)handler).Invoke(KoikatuAPI.Instance, args); } catch (Exception e) { KoikatuAPI.Log(LogLevel.Error, e); } } } }
private static void ReloadChara(ChaControl chaControl = null) { if (IsCurrentlyReloading(chaControl)) { return; } if (chaControl == null) { _currentlyReloading.UnionWith(ChaControls); } else { _currentlyReloading.Add(chaControl); } KoikatuAPI.Log(LogLevel.Debug, "[KKAPI] Character load/reload: " + GetLogName(chaControl)); // Always send events to controllers before subscribers of CharacterReloaded var gamemode = KoikatuAPI.GetCurrentGameMode(); foreach (var behaviour in GetBehaviours(chaControl)) { behaviour.OnReloadInternal(gamemode); } try { CharacterReloaded?.Invoke(null, new CharaReloadEventArgs(chaControl)); } catch (Exception e) { KoikatuAPI.Log(LogLevel.Error, e); } if (chaControl == null) { _currentlyReloading.Clear(); } else { _currentlyReloading.Remove(chaControl); } }
public static void ChaFile_CopyChaFileHook(ChaFile dst, ChaFile src) { foreach (var handler in _registeredHandlers) { if (handler.ExtendedDataCopier == null) { continue; } try { handler.ExtendedDataCopier(dst, src); } catch (Exception e) { KoikatuAPI.Log(LogLevel.Error, e); } } }
private static void OnMakerExiting() { if (MakerExiting != null) { foreach (var handler in MakerExiting.GetInvocationList()) { try { ((EventHandler)handler).Invoke(KoikatuAPI.Instance, EventArgs.Empty); } catch (Exception e) { KoikatuAPI.Log(LogLevel.Error, e); } } } RemoveCustomControls(); }
private static void OnMakerFinishedLoading() { if (MakerFinishedLoading != null) { foreach (var handler in MakerFinishedLoading.GetInvocationList()) { try { ((EventHandler)handler).Invoke(KoikatuAPI.Instance, EventArgs.Empty); } catch (Exception e) { KoikatuAPI.Log(LogLevel.Error, e); } } } _makerLoaded = true; }
private static void CreateCustomControlsInCategory(Transform categoryTransfrom) { foreach (var subCategoryGroup in _guiEntries .Where(x => x.Category.CategoryName == categoryTransfrom.name) .GroupBy(x => x.Category.SubCategoryName)) { var categorySubTransform = categoryTransfrom.Find(subCategoryGroup.Key); if (categorySubTransform != null) { CreateCustomControlsInSubCategory(categorySubTransform, subCategoryGroup.ToList()); } else { KoikatuAPI.Log(LogLevel.Error, $"[MakerAPI] Failed to add {subCategoryGroup.Count()} custom controls " + $"to {categoryTransfrom.name}/{subCategoryGroup.Key} - The category was not registered with AddSubCategory."); } } }
private static void OnAccessoryKindChanged(object source, int slotNo) { if (KoikatuAPI.EnableDebugLogging) { KoikatuAPI.Log(LogLevel.Message, "AccessoryKindChanged - slot:" + slotNo); } if (AccessoryKindChanged == null) { return; } try { AccessoryKindChanged(source, new AccessorySlotEventArgs(slotNo)); } catch (Exception ex) { KoikatuAPI.Log(LogLevel.Error, "Subscription to AccessoryKindChanged crashed: " + ex); } }
private static void OnRegisterCustomSubCategories() { MakerLoadToggle.Setup(); MakerCoordinateLoadToggle.Setup(); if (RegisterCustomSubCategories != null) { var args = new RegisterSubCategoriesEvent(); foreach (var handler in RegisterCustomSubCategories.GetInvocationList()) { try { ((EventHandler <RegisterSubCategoriesEvent>)handler).Invoke(KoikatuAPI.Instance, args); } catch (Exception e) { KoikatuAPI.Log(LogLevel.Error, e); } } } }
private static void CreateOrAddBehaviours(ChaControl target) { foreach (var handler in _registeredHandlers) { var existing = target.gameObject.GetComponents(handler.ControllerType) .Cast <CharaCustomFunctionController>() .FirstOrDefault(x => x.ExtendedDataId == handler.ExtendedDataId); if (existing == null) { try { handler.CreateInstance(target); } catch (Exception e) { KoikatuAPI.Log(LogLevel.Error, e); } } } }
private static void OnChangeAcs(CvsAccessoryChange instance) { if (AccessoryTransferred == null) { return; } try { var traverse = Traverse.Create(instance); var selSrc = traverse.Field("selSrc").GetValue <int>(); var selDst = traverse.Field("selDst").GetValue <int>(); var args = new AccessoryTransferEventArgs(selSrc, selDst); AccessoryTransferred(instance, args); } catch (Exception ex) { KoikatuAPI.Log(LogLevel.Error, "Crash in AccessoryTransferred event: " + ex); } }
private static void OnMakerAccSlotAdded(object source, int newSlotIndex, Transform newSlotTransform) { if (KoikatuAPI.EnableDebugLogging) { KoikatuAPI.Log(LogLevel.Message, "MakerAccSlotAdded - slot:" + newSlotIndex); } MakerAPI.OnMakerAccSlotAdded(newSlotTransform); if (MakerAccSlotAdded == null) { return; } try { MakerAccSlotAdded(source, new AccessorySlotEventArgs(newSlotIndex)); } catch (Exception ex) { KoikatuAPI.Log(LogLevel.Error, "Subscription to SelectedMakerSlot crashed: " + ex); } }