private static void ApplyToAllDatas(Func <SaveData.CharaData, PregnancyData, bool> action) { void ApplyToDatas(SaveData.CharaData character) { var chafiles = character.GetRelatedChaFiles(); if (chafiles == null) { return; } foreach (var chaFile in chafiles) { var data = ExtendedSave.GetExtendedDataById(chaFile, PregnancyPlugin.GUID); var pd = PregnancyData.Load(data) ?? new PregnancyData(); if (action(character, pd)) { ExtendedSave.SetExtendedDataById(chaFile, PregnancyPlugin.GUID, pd.Save()); } } } foreach (var heroine in Game.Instance.HeroineList) { ApplyToDatas(heroine); } ApplyToDatas(Game.Instance.Player); // If controller exists then update its state so it gets any pregnancy week updates foreach (var controller in FindObjectsOfType <PregnancyCharaController>()) { controller.ReadData(); } }
protected override void OnCardBeingSaved(GameMode currentGameMode) { var pd = new PluginData { version = 2 }; OverlayStorage.Save(pd); #if !EC if (!EnableInStudioSkin) { pd.data[nameof(EnableInStudioSkin)] = EnableInStudioSkin; } if (!EnableInStudioIris) { pd.data[nameof(EnableInStudioIris)] = EnableInStudioIris; } #endif SetExtendedData(pd.data.Count > 0 ? pd : null); #if KK || KKS ExtendedSave.SetExtendedDataById(ChaFileControl, "com.jim60105.kk.charaoverlaysbasedoncoordinate", null); #endif }
private static void ApplyToAllDatas(Func<AgentData, PregnancyData, bool> action) { void ApplyToDatas(AgentData character) { var chafiles = character.GetRelatedChaFiles(); if (chafiles == null) return; foreach (var chaFile in chafiles) { var data = ExtendedSave.GetExtendedDataById(chaFile, PregnancyPlugin.GUID); var pd = PregnancyData.Load(data) ?? new PregnancyData(); if (action(character, pd)) ExtendedSave.SetExtendedDataById(chaFile, PregnancyPlugin.GUID, pd.Save()); } } var heroineList = GetHeroineList(); if (heroineList == null) return; foreach (var heroine in heroineList) { ApplyToDatas(heroine); } // ApplyToDatas(Singleton<Map>.Instance.Player.AgentPartner.AgentData); TODO find male AgentData, if we want to match what KK is now doing // If controller exists then update its state so it gets any pregnancy week updates foreach (var controller in FindObjectsOfType<PregnancyCharaController>()) controller.ReadData(); }
private static void ExtendedWorldLoad(WorldData worldData) { if (worldData == null) { return; } var extResolve = ExtendedSave.GetExtendedDataById(worldData, UARExtID); if (extResolve == null) { return; } foreach (var dataPair in extResolve.data) { if (int.TryParse(dataPair.Key, out int id) && worldData.HousingData.CraftInfos.TryGetValue(id, out var craftInfo) && dataPair.Value is Dictionary <string, object> extData) { ExtendedSave.SetExtendedDataById(craftInfo, UARExtID, new PluginData() { data = extData }); ExtendedHousingLoad(craftInfo); } } }
/// <summary> /// Save your custom data to the character card under the ID you specified when registering this controller. /// This should be used inside the <see cref="OnCardBeingSaved"/> event. /// Consider using one of the other "Get___ExtData" and "Set___ExtData" methods instead since they are more reliable and handle copying and transferring outfits and they conform to built in maker load toggles. /// </summary> /// <param name="data">Your custom data to be written to the character card. Can be null to remove the data.</param> public void SetExtendedData(PluginData data) { if (ExtendedDataId == null) { throw new ArgumentException(nameof(ExtendedDataId)); } ExtendedSave.SetExtendedDataById(ChaFileControl, ExtendedDataId, data); #if KK //todo || KKS // Needed for propagating changes back to the original charFile since they don't get copied back. var heroine = ChaControl.GetHeroine(); if (heroine != null) { ExtendedSave.SetExtendedDataById(heroine.charFile, ExtendedDataId, data); if (ChaControl != heroine.chaCtrl) { ExtendedSave.SetExtendedDataById(heroine.chaCtrl.chaFile, ExtendedDataId, data); // Update other instance to reflect the new ext data var other = heroine.chaCtrl.GetComponent(GetType()) as CharaCustomFunctionController; if (other != null) { other.OnReloadInternal(KoikatuAPI.GetCurrentGameMode()); } } var npc = heroine.GetNPC(); if (npc != null && npc.chaCtrl != null && npc.chaCtrl != ChaControl) { ExtendedSave.SetExtendedDataById(npc.chaCtrl.chaFile, ExtendedDataId, data); // Update other instance to reflect the new ext data var other = npc.chaCtrl.GetComponent(GetType()) as CharaCustomFunctionController; if (other != null) { other.OnReloadInternal(KoikatuAPI.GetCurrentGameMode()); } } } var player = ChaControl.GetPlayer(); if (player != null) { ExtendedSave.SetExtendedDataById(player.charFile, ExtendedDataId, data); if (ChaControl != player.chaCtrl) { ExtendedSave.SetExtendedDataById(player.chaCtrl.chaFile, ExtendedDataId, data); // Update other instance to reflect the new ext data var other = player.chaCtrl.GetComponent(GetType()) as CharaCustomFunctionController; if (other != null) { other.OnReloadInternal(KoikatuAPI.GetCurrentGameMode()); } } } #endif }
/// <summary> /// Card saving /// </summary> private void ExtendedCardSave(ChaFile file) { PluginData ExtendedData = ExtendedSave.GetExtendedDataById(file, "KK_FutaMod"); if (ExtendedData != null && ExtendedData.data.ContainsKey("Futa")) { if (Singleton <CustomBase> .IsInstance() && Singleton <CustomBase> .Instance.chaCtrl != null) { //Saving card from chara maker, get the status from the character ExtendedData.data["Futa"] = file.status.visibleSonAlways; ExtendedSave.SetExtendedDataById(file, "KK_FutaMod", ExtendedData); } else { //Not in chara maker, keep the existing extended data ExtendedSave.SetExtendedDataById(file, "KK_FutaMod", ExtendedData); } } else { if (Singleton <CustomBase> .IsInstance() && Singleton <CustomBase> .Instance.chaCtrl != null) { //Saving a character in chara maker that doesn't have extended data ExtendedData = new PluginData(); ExtendedData.data = new Dictionary <string, object> { { "Futa", file.status.visibleSonAlways } }; ExtendedSave.SetExtendedDataById(file, "KK_FutaMod", ExtendedData); } } }
/// <summary> /// 將dict存入ChaControl ExtendedData /// </summary> /// <param name="chaCtrl">目標ChaControl</param> /// <param name="dict">要存入的dict</param> public static void SetToExtData(ChaControl chaCtrl, Dictionary <int, object> dict) { PluginData data = new PluginData(); Dictionary <int, Dictionary <int, object> > allExtData = GetDataFromExtData(chaCtrl, out _); int coorType = chaCtrl.fileStatus.coordinateType; if ((null == dict || dict.Count == 0) && (null == allExtData || allExtData.Count == 0)) { data = null; } else { if (null == allExtData) { allExtData = new Dictionary <int, Dictionary <int, object> >(); Logger.LogDebug($"HairAccCustomizer info not found while saving."); } if (allExtData.ContainsKey(coorType)) { allExtData[coorType].Clear(); allExtData.Remove(coorType); } if (null != dict) { allExtData[coorType] = dict; } data.data.Add("HairAccessories", MessagePackSerializer.Serialize(allExtData)); } ExtendedSave.SetExtendedDataById(chaCtrl.chaFile, GUID, data); }
private static void AddPregnancyWeek(ChaFileControl chaFile) { var data = ExtendedSave.GetExtendedDataById(chaFile, PregnancyPlugin.GUID); if (data == null) { return; } PregnancyDataUtils.DeserializeData(data, out var week, out var gameplayEnabled, out var fertility, out var schedule); // Advance the week of pregnancy. If week is 0 the character is not pregnant if (gameplayEnabled && week > 0) { if (week < PregnancyDataUtils.LeaveSchoolWeek) { // Advance through in-school at full configured speed var weekChange = PregnancyPlugin.PregnancyProgressionSpeed.Value; week = Mathf.Min(PregnancyDataUtils.LeaveSchoolWeek, week + weekChange); } else if (week < PregnancyDataUtils.ReturnToSchoolWeek) { // Make sure at least one week is spent out of school var weekChange = Mathf.Min(PregnancyDataUtils.ReturnToSchoolWeek - PregnancyDataUtils.LeaveSchoolWeek - 1, PregnancyPlugin.PregnancyProgressionSpeed.Value); week = week + weekChange; } if (week >= PregnancyDataUtils.ReturnToSchoolWeek) { week = 0; } //Logger.Log(LogLevel.Debug, $"Preg - pregnancy week for {chaFile.parameter.fullname} is now {week}"); ExtendedSave.SetExtendedDataById(chaFile, PregnancyPlugin.GUID, PregnancyDataUtils.SerializeData(week, true, fertility, schedule)); } }
/// <summary> /// Save your custom data to the character card under the ID you specified when registering this controller. /// </summary> /// <param name="data">Your custom data to be written to the character card. Can be null to remove the data.</param> public void SetExtendedData(PluginData data) { if (ExtendedDataId == null) { throw new ArgumentException(nameof(ExtendedDataId)); } ExtendedSave.SetExtendedDataById(ChaFileControl, ExtendedDataId, data); }
/// <summary> /// Save your custom data to the character card under the ID you specified when registering this controller. /// </summary> /// <param name="data">Your custom data to be written to the character card. Can be null to remove the data.</param> public void SetExtendedData(PluginData data) { if (ExtendedDataId == null) { throw new ArgumentException(nameof(ExtendedDataId)); } ExtendedSave.SetExtendedDataById(Manager.Game.Instance.saveData, ExtendedDataId, data); }
public static void CopyChaFile(ChaFile dst, ChaFile src) { PluginData ExtendedData = ExtendedSave.GetExtendedDataById(src, "KK_FutaMod"); if (ExtendedData != null && ExtendedData.data.ContainsKey("Futa")) { ExtendedSave.SetExtendedDataById(dst, "KK_FutaMod", ExtendedData); } }
/// <summary> /// Set extended data to the specified coordinate by using the ID you specified when registering this controller. /// This should be used inside the <see cref="OnCoordinateBeingSaved"/> event. /// Consider using one of the other "Get___ExtData" and "Set___ExtData" methods instead since they are more reliable and handle copying and transferring outfits and they conform to built in maker load toggles. /// </summary> /// <param name="coordinate">Coordinate you want to set the data to</param> /// <param name="data">Your custom data to be saved to the coordinate card</param> public void SetCoordinateExtendedData(ChaFileCoordinate coordinate, PluginData data) { if (coordinate == null) { throw new ArgumentNullException(nameof(coordinate)); } if (ExtendedDataId == null) { throw new ArgumentException(nameof(ExtendedDataId)); } ExtendedSave.SetExtendedDataById(coordinate, ExtendedDataId, data); }
private static void ExtendedCardSave(ChaFile file) { List <ResolveInfo> resolutionInfo = new List <ResolveInfo>(); void IterateStruct(object obj, Dictionary <CategoryProperty, StructValue <int> > dict, string propertyPrefix = "") { foreach (var kv in dict) { int slot = kv.Value.GetMethod(obj); var info = UniversalAutoResolver.LoadedResolutionInfo.FirstOrDefault(x => x.Property == kv.Key.ToString() && x.LocalSlot == slot); if (info != null) { var newInfo = info.DeepCopy(); newInfo.Property = $"{propertyPrefix}{newInfo.Property}"; kv.Value.SetMethod(obj, newInfo.Slot); resolutionInfo.Add(newInfo); } } } IterateStruct(file.custom.face, StructReference.ChaFileFaceProperties); IterateStruct(file.custom.body, StructReference.ChaFileBodyProperties); IterateStruct(file.custom.hair, StructReference.ChaFileHairProperties); for (int i = 0; i < file.coordinate.Length; i++) { var coordinate = file.coordinate[i]; string prefix = $"outfit{i}."; IterateStruct(coordinate.clothes, StructReference.ChaFileClothesProperties, prefix); IterateStruct(coordinate.makeup, StructReference.ChaFileMakeupProperties, prefix); for (int acc = 0; acc < coordinate.accessory.parts.Length; acc++) { string accPrefix = $"{prefix}accessory{acc}."; IterateStruct(coordinate.accessory.parts[acc], StructReference.ChaFileAccessoryPartsInfoProperties, accPrefix); } } ExtendedSave.SetExtendedDataById(file, UniversalAutoResolver.UARExtID, new PluginData { data = new Dictionary <string, object> { { "info", resolutionInfo.Select(x => x.Serialize()).ToList() } } }); }
/// <summary> /// Register new functionality that will be automatically added to all characters (where applicable). /// Offers easy API for saving and loading extended data, and for running logic to apply it to the characters. /// All necessary hooking and event subscribing is done for you. All you have to do is create a type /// that inherits from <code>CharaExtraBehaviour</code> (don't make instances, the API will make them for you). /// </summary> /// <typeparam name="T">Type with your custom logic to add to a character</typeparam> /// <param name="extendedDataId">Extended data ID used by this behaviour. Set to null if not used. Needed to copy the data in some situations.</param> public static void RegisterExtraBehaviour <T>(string extendedDataId) where T : CharaCustomFunctionController, new() { void BasicCopier(ChaFile dst, ChaFile src) { var extendedData = ExtendedSave.GetExtendedDataById(src, extendedDataId); ExtendedSave.SetExtendedDataById(dst, extendedDataId, extendedData); } var copier = extendedDataId == null ? (CopyExtendedDataFunc)null : BasicCopier; RegisterExtraBehaviour <T>(extendedDataId, copier); }
/// <summary> /// Save your custom data to the character card under the ID you specified when registering this controller. /// This should be used inside the <see cref="OnCardBeingSaved"/> event. /// Consider using one of the other "Get___ExtData" and "Set___ExtData" methods instead since they are more reliable and handle copying and transferring outfits and they conform to built in maker load toggles. /// </summary> /// <param name="data">Your custom data to be written to the character card. Can be null to remove the data.</param> public void SetExtendedData(PluginData data) { if (ExtendedDataId == null) { throw new ArgumentException(nameof(ExtendedDataId)); } ExtendedSave.SetExtendedDataById(ChaFileControl, ExtendedDataId, data); #if KK || KKS if (KoikatuAPI.GetCurrentGameMode() == GameMode.MainGame) { // In main game store ext data for the character inside of the main chaFile object (the one that gets saved to game saves). // This allows saving ext data inside talk scenes and H scenes without losing it after exiting to main map. var heroine = ChaControl.GetHeroine(); if (heroine != null) { ExtendedSave.SetExtendedDataById(heroine.charFile, ExtendedDataId, data); if (ChaControl != heroine.chaCtrl) { ExtendedSave.SetExtendedDataById(heroine.chaCtrl.chaFile, ExtendedDataId, data); // Update other instance to reflect the new ext data CharacterApi.Hooks.SetDirty(heroine, true); } var npc = heroine.GetNPC(); if (npc != null && npc.chaCtrl != null && npc.chaCtrl != ChaControl && npc.chaCtrl != heroine.chaCtrl) { ExtendedSave.SetExtendedDataById(npc.chaCtrl.chaFile, ExtendedDataId, data); // Update other instance to reflect the new ext data CharacterApi.Hooks.SetDirty(heroine, true); } } else { var player = ChaControl.GetPlayer(); if (player != null) { ExtendedSave.SetExtendedDataById(player.charFile, ExtendedDataId, data); if (ChaControl != player.chaCtrl) { ExtendedSave.SetExtendedDataById(player.chaCtrl.chaFile, ExtendedDataId, data); // Update other instance to reflect the new ext data CharacterApi.Hooks.SetDirty(player, true); } } } } #endif }
private static void StartPregnancy(ChaFileControl chaFile) { var data = ExtendedSave.GetExtendedDataById(chaFile, PregnancyPlugin.GUID); PregnancyDataUtils.DeserializeData(data, out var week, out var gameplayEnabled, out var fertility, out var schedule); // If week is 0 the character is not pregnant if (gameplayEnabled && week <= 0) { //Logger.Log(LogLevel.Debug, "Preg - starting pregnancy on " + chaFile.parameter.fullname + ", new week is " + 1); ExtendedSave.SetExtendedDataById(chaFile, PregnancyPlugin.GUID, PregnancyDataUtils.SerializeData(1, true, fertility, schedule)); } }
public static void KCOXCoordToChara(ChaFileCoordinate coord, ChaFileControl chara) { Dictionary <string, ClothesTexData> coordData = coord.KCOXData().KCOXCoordinateDictionary(); Dictionary <ChaFileDefine.CoordinateType, Dictionary <string, ClothesTexData> > charaData = chara.KCOXData().KCOXDictionary(); ChaFileDefine.CoordinateType[] coordinates = Enum.GetValues(typeof(ChaFileDefine.CoordinateType)) as ChaFileDefine.CoordinateType[]; foreach (ChaFileDefine.CoordinateType coordinate in coordinates) { charaData[coordinate] = coordData; } ExtendedSave.SetExtendedDataById(chara, KoiClothesOverlayMgr.GUID, charaData.KCOXData()); }
internal static void ExtendedHousingSave(CraftInfo info) { if (info == null) { return; } if (Sideloader.DebugLoggingResolveInfo.Value) { Sideloader.Logger.LogInfo($"Embedding Resolving Data to Housing"); } Dictionary <string, object> ExtendedData = new Dictionary <string, object>(); // housing data has no "key" so it's almost nightmare to resolve the id. // and worst of all, the structure of housing card object info is nested. // so what im going to do is generating iteration index which can be used for distinguishing objects int index = 0; Dictionary <int, byte[]> ResolutionInfos = new Dictionary <int, byte[]>(); HousingObjectIteration <byte[]>(info.ObjectInfos, ref index, ResolutionInfos, (item) => { var extResolve = LoadedMainGameResolutionInfo.Where(x => x.LocalSlot == item.ID).FirstOrDefault(); if (extResolve != null) { return(new AIGameResolveInfo { GUID = extResolve.GUID, Slot = extResolve.Slot, // I need to know the history about "hard mod compatibility" LocalSlot = extResolve.LocalSlot, }.Serialize()); } else { return(null); } }); ExtendedData.Add(mapItemInfoKey, ResolutionInfos); ExtendedSave.SetExtendedDataById(info, UARExtID, new PluginData() { data = ExtendedData }); }
private static void ExtendedCardSave(ChaFile file) { List <ResolveInfo> resolutionInfo = new List <ResolveInfo>(); void IterateStruct(object obj, Dictionary <CategoryProperty, StructValue <int> > dict) { foreach (var kv in dict) { int slot = kv.Value.GetMethod(obj); var info = UniversalAutoResolver.LoadedResolutionInfo.FirstOrDefault(x => x.Property == kv.Key.ToString() && x.LocalSlot == slot); if (info != null) { kv.Value.SetMethod(obj, info.Slot); resolutionInfo.Add(info); } } } IterateStruct(file.custom.face, StructReference.ChaFileFaceProperties); IterateStruct(file.custom.body, StructReference.ChaFileBodyProperties); IterateStruct(file.custom.hair, StructReference.ChaFileHairProperties); //foreach (var coordinate in file.coordinate) //{ // IterateStruct(file.coordinate., StructReference.ChaFileFaceProperties); // IterateStruct(file.custom.face, StructReference.ChaFileFaceProperties); //} ExtendedSave.SetExtendedDataById(file, UniversalAutoResolver.UARExtID, new PluginData { data = new Dictionary <string, object> { { "info", resolutionInfo.Select(x => x.Serialize()).ToList() } } }); }
private static void ExtendedWorldSave(WorldData worldData) { if (worldData == null) { return; // just in case } // ugh... I don't think this is good idea // also this code can't resolve world-level resolve.. if there will be any.. Dictionary <string, object> ExtendedData = new Dictionary <string, object>(); foreach (var craftInfoPairs in worldData.HousingData.CraftInfos) { ExtendedHousingSave(craftInfoPairs.Value); var data = ExtendedSave.GetExtendedDataById(craftInfoPairs.Value, UARExtID).data; ExtendedData.Add(craftInfoPairs.Key.ToString(), data); } ExtendedSave.SetExtendedDataById(worldData, UARExtID, new PluginData() { data = ExtendedData }); }
private static void ExtendedCardSave(ChaFile file) { List <ResolveInfo> resolutionInfo = new List <ResolveInfo>(); void IterateStruct(Dictionary <CategoryProperty, StructValue <int> > dict, object obj, IEnumerable <ResolveInfo> extInfo, string propertyPrefix = "") { foreach (var kv in dict) { int slot = kv.Value.GetMethod(obj); var info = UniversalAutoResolver.LoadedResolutionInfo.FirstOrDefault(x => x.Property == kv.Key.ToString() && x.LocalSlot == slot); if (info == null) { continue; } var newInfo = info.DeepCopy(); newInfo.Property = $"{propertyPrefix}{newInfo.Property}"; kv.Value.SetMethod(obj, newInfo.Slot); resolutionInfo.Add(newInfo); } } IterateCardPrefixes(IterateStruct, file, null); ExtendedSave.SetExtendedDataById(file, UniversalAutoResolver.UARExtID, new PluginData { data = new Dictionary <string, object> { ["info"] = resolutionInfo.Select(x => x.Serialize()).ToList() } }); }
/// <summary> /// Register the futa checkbox with MakerAPI /// </summary> private void AddCategory(object sender, RegisterSubCategoriesEvent args) { if (Singleton <CustomBase> .Instance.modeSex == 0) { return; } FutaToggle = args.AddControl(new MakerToggle(MakerConstants.Body.All, "ふたなり", this)); void ToggleFuta(bool IsFuta) { Singleton <CustomBase> .Instance.chaCtrl.chaFile.status.visibleSonAlways = IsFuta; PluginData ExtendedData = new PluginData(); ExtendedData.data = new Dictionary <string, object> { { "Futa", IsFuta } }; ExtendedSave.SetExtendedDataById(Singleton <CustomBase> .Instance.chaCtrl.chaFile, "KK_FutaMod", ExtendedData); } var obs = Observer.Create <bool>(ToggleFuta); FutaToggle.ValueChanged.Subscribe(obs); FutaToggle.Value = Singleton <CustomBase> .Instance.chaCtrl.chaFile.status.visibleSonAlways; }
public static void KKABMXCoordToChara(ChaFileCoordinate coord, ChaFileControl chara) { Dictionary <string, BoneModifierData> coordData = coord.KKABMXData().BoneModifierData(); List <BoneModifier> charaData = chara.KKABMXData().BoneModifiers(); foreach (KeyValuePair <string, BoneModifierData> pair in coordData) { BoneModifier modifier = charaData.FirstOrDefault(v => v.BoneName == pair.Key); if (modifier == null) { charaData.Add(modifier = new BoneModifier(pair.Key)); } modifier.MakeCoordinateSpecific(); for (int i = 0; i < modifier.CoordinateModifiers.Length; i++) { modifier.CoordinateModifiers[i] = pair.Value; } } ExtendedSave.SetExtendedDataById(chara, KKABMX_Core.ExtDataGUID, charaData.KKABMXData()); }
private static void CopyCoordExtData(ChaFileCoordinate fromCoord, ChaFileCoordinate toCoord) { // Clear old ext data var oldData = ExtendedSave.GetAllExtendedData(toCoord); if (oldData != null) { foreach (var data in oldData.ToList()) { ExtendedSave.SetExtendedDataById(toCoord, data.Key, null); } } // Copy new ext data from the coordinate that is about to be swapped in var newData = ExtendedSave.GetAllExtendedData(fromCoord); if (newData != null) { foreach (var data in newData.ToList()) { ExtendedSave.SetExtendedDataById(toCoord, data.Key, data.Value); } } }
/// <summary> /// Set extended data for this coordinate card /// </summary> /// <param name="dataId">Key to save the data under (usually plugin GUID)</param> /// <param name="data">Data to set</param> public void SetCoordinateExtData(string dataId, PluginData data) => ExtendedSave.SetExtendedDataById(LoadedCoordinate, dataId, data);
internal static void ExtendedCardSave(ChaFile file) { if (DoingImport) { return; } List <ResolveInfo> resolutionInfo = new List <ResolveInfo>(); void IterateStruct(Dictionary <CategoryProperty, StructValue <int> > dict, object obj, IEnumerable <ResolveInfo> extInfo, string propertyPrefix = "") { foreach (var kv in dict) { int slot = kv.Value.GetMethod(obj); //No need to attempt a resolution info lookup for empty accessory slots and pattern slots if (slot == 0) { continue; } //Check if it's a vanilla item if (slot < BaseSlotID) #if KK || EC { if (Lists.InternalDataList[kv.Key.Category].ContainsKey(slot)) #elif AI { if (Lists.InternalDataList[(int)kv.Key.Category].ContainsKey(slot)) #endif { continue; } } //For accessories, make sure we're checking the appropriate category if (kv.Key.Category.ToString().Contains("ao_")) { ChaFileAccessory.PartsInfo AccessoryInfo = (ChaFileAccessory.PartsInfo)obj; if ((int)kv.Key.Category != AccessoryInfo.type) { //If the current category does not match the accessory's category do not attempt a resolution info lookup continue; } } var info = TryGetResolutionInfo(kv.Key.ToString(), slot); if (info == null) { continue; } var newInfo = info.DeepCopy(); newInfo.Property = $"{propertyPrefix}{newInfo.Property}"; kv.Value.SetMethod(obj, newInfo.Slot); resolutionInfo.Add(newInfo); } } IterateCardPrefixes(IterateStruct, file, null); ExtendedSave.SetExtendedDataById(file, UARExtID, new PluginData { data = new Dictionary <string, object> { ["info"] = resolutionInfo.Select(x => x.Serialize()).ToList() } }); }
/// <summary> /// Sets the visibility state of a character. If no optional parameters are set the character's visiblity state will be read from the character file and set from that. /// </summary> /// <param name="chaControl">Character for which to set visible state.</param> /// <param name="toggleVisible">Toggles the character from visible to invisible and vice versa. Not used if forceVisible is set.</param> /// <param name="forceVisible">Forces the character to the state set in forceVisibleState. Overrides default visibility state and toggleVisible.</param> /// <param name="forceVisibleState">The visibility state to set a character. Only used if forceVisible is set.</param> /// <param name="saveVisibleState">Whether or not the visible state should be saved to the card.</param> private static void SetVisibleState(ChaControl chaControl, bool toggleVisible = false, bool forceVisible = false, bool forceVisibleState = false, bool saveVisibleState = true) { bool Visible; PluginData ExtendedData = ExtendedSave.GetExtendedDataById(chaControl.chaFile, "KK_InvisibleBody"); GameObject CharacterObject = GameObject.Find(chaControl.name); if (ExtendedData == null) { Logger.Log(LogLevel.Debug, "No KK_InvisibleBody marker found"); Visible = true; //character has no extended data, create some so it will save and load with the scene ExtendedData = new PluginData(); Dictionary <string, object> dic = new Dictionary <string, object> { { "Visible", Visible } }; ExtendedData.data = dic; } else { Logger.Log(LogLevel.Debug, $"KK_InvisibleBody marker found, Visible was {ExtendedData.data["Visible"]}"); Visible = (bool)ExtendedData.data["Visible"]; } if (forceVisible) { Visible = forceVisibleState; } else if (toggleVisible) { Visible = !Visible; } if (saveVisibleState) { ExtendedData.data["Visible"] = Visible; ExtendedSave.SetExtendedDataById(chaControl.chaFile, "KK_InvisibleBody", ExtendedData); } //No need to IterateVisible for visible characters that haven't changed if (!(Visible == true && toggleVisible == false && forceVisible == false)) { Transform cf_j_root = CharacterObject.transform.Find("BodyTop/p_cf_body_bone/cf_j_root"); if (cf_j_root != null) { IterateVisible(cf_j_root.gameObject, Visible); } //female Transform cf_o_rootf = CharacterObject.transform.Find("BodyTop/p_cf_body_00/cf_o_root/"); if (cf_o_rootf != null) { IterateVisible(cf_o_rootf.gameObject, Visible); } //male Transform cf_o_rootm = CharacterObject.transform.Find("BodyTop/p_cm_body_00/cf_o_root/"); if (cf_o_rootm != null) { IterateVisible(cf_o_rootm.gameObject, Visible); } } }
private static void ExtendedCoordinateSave(ChaFileCoordinate file) { List <ResolveInfo> resolutionInfo = new List <ResolveInfo>(); void IterateStruct(Dictionary <CategoryProperty, StructValue <int> > dict, object obj, IEnumerable <ResolveInfo> extInfo, string propertyPrefix = "") { foreach (var kv in dict) { int slot = kv.Value.GetMethod(obj); //No need to attempt a resolution info lookup for empty accessory slots and pattern slots if (slot == 0) { continue; } //Check if it's a vanilla item if (slot < 100000000) { if (ResourceRedirector.ListLoader.InternalDataList[kv.Key.Category].ContainsKey(slot)) { continue; } } //For accessories, make sure we're checking the appropriate category if (kv.Key.Category.ToString().Contains("ao_")) { ChaFileAccessory.PartsInfo AccessoryInfo = (ChaFileAccessory.PartsInfo)obj; if ((int)kv.Key.Category != AccessoryInfo.type) { //If the current category does not match the accessory's category do not attempt a resolution info lookup continue; } } var info = UniversalAutoResolver.LoadedResolutionInfo.FirstOrDefault(x => x.Property == kv.Key.ToString() && x.LocalSlot == slot); if (info == null) { continue; } var newInfo = info.DeepCopy(); newInfo.Property = $"{propertyPrefix}{newInfo.Property}"; kv.Value.SetMethod(obj, newInfo.Slot); resolutionInfo.Add(newInfo); } } IterateCoordinatePrefixes(IterateStruct, file, null); ExtendedSave.SetExtendedDataById(file, UniversalAutoResolver.UARExtID, new PluginData { data = new Dictionary <string, object> { ["info"] = resolutionInfo.Select(x => x.Serialize()).ToList() } }); }
/// <summary> /// 載入擴充資料 /// </summary> /// <param name="ocichar">要被替換的對象</param> /// <param name="file">新角色存檔路徑</param> /// <param name="sex">性別</param> /// <returns></returns> private static bool LoadExtendedData(OCIChar ocichar, string file, byte sex) { ChaFileControl tmpChaFile = new ChaFileControl(); tmpChaFile.LoadCharaFile(file, sex); foreach (string ext in KK_StudioCharaOnlyLoadBody.ExtendedDataToCopy) { switch (ext) { case "KKABMPlugin.ABMData": #region ABMX //取得BoneController MonoBehaviour BoneController = ocichar.charInfo.GetComponents <MonoBehaviour>().FirstOrDefault(x => Equals(x.GetType().Namespace, "KKABMX.Core")); if (null == BoneController) { Logger.LogDebug("No ABMX BoneController found"); break; } //建立重用function void GetModifiers(Action <object> action) { foreach (string boneName in (IEnumerable <string>)BoneController.Invoke("GetAllPossibleBoneNames")) { object modifier = BoneController.Invoke("GetModifier", new object[] { boneName }); if (null != modifier) { action(modifier); } } } //取得舊角色衣服ABMX數據 List <object> previousModifier = new List <object>(); GetModifiers(x => { if ((bool)x.Invoke("IsCoordinateSpecific")) { previousModifier.Add(x); } }); //將擴充資料由暫存複製到角色身上 ExtendedSave.SetExtendedDataById(ocichar.charInfo.chaFile, ext, ExtendedSave.GetExtendedDataById(tmpChaFile, ext)); //把擴充資料載入ABMX插件 BoneController.Invoke("OnReload", new object[] { 2, false }); //清理新角色數據,將衣服數據刪除 List <object> newModifiers = new List <object>(); int i = 0; GetModifiers(x => { if ((bool)x.Invoke("IsCoordinateSpecific")) { Logger.LogDebug("Clean new coordinate ABMX BoneData: " + (string)x.GetProperty("BoneName")); x.Invoke("MakeNonCoordinateSpecific"); object y = x.Invoke("GetModifier", new object[] { (ChaFileDefine.CoordinateType) 0 }); y.Invoke("Clear"); x.Invoke("MakeCoordinateSpecific"); //保險起見以免後面沒有成功清除 i++; } else { newModifiers.Add(x); } }); //將舊的衣服數據合併回到角色身上 i = 0; foreach (object modifier in previousModifier) { string bonename = (string)modifier.GetProperty("BoneName"); if (!newModifiers.Any(x => string.Equals(bonename, (string)x.GetProperty("BoneName")))) { BoneController.Invoke("AddModifier", new object[] { modifier }); Logger.LogDebug("Rollback cooridnate ABMX BoneData: " + bonename); } else { Logger.LogError("Duplicate coordinate ABMX BoneData: " + bonename); } i++; } Logger.LogDebug($"Merge {i} previous ABMX Bone Modifiers"); //重整 BoneController.SetProperty("NeedsFullRefresh", true); BoneController.SetProperty("NeedsBaselineUpdate", true); BoneController.Invoke("LateUpdate"); //把ABMX的數據存進擴充資料 BoneController.Invoke("OnCardBeingSaved", new object[] { 1 }); BoneController.Invoke("OnReload", new object[] { 2, false }); ////列出角色身上所有ABMX數據 //Logger.LogDebug("--List all exist ABMX BoneData--"); //foreach (string boneName in (IEnumerable<string>)BoneController.Invoke("GetAllPossibleBoneNames", null)) { // object modifier = BoneController.Invoke("GetModifier", new object[] { boneName }); // if (null != modifier) { // Logger.LogDebug(boneName); // } //} //Logger.LogDebug("--List End--"); break; #endregion case "com.bepis.sideloader.universalautoresolver": #region SideloaderUAS //判斷CategoryNo分類function bool isBelongsToCharaBody(ChaListDefine.CategoryNo categoryNo) { Type StructReference = typeof(UniversalAutoResolver).Assembly.GetType("Sideloader.AutoResolver.StructReference"); return(StructReference.GetProperty("ChaFileFaceProperties", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy).GetValue(StructReference, null).ToDictionary <object, object>().Keys.Any(x => (ChaListDefine.CategoryNo)x.GetField("Category") == categoryNo) || StructReference.GetProperty("ChaFileBodyProperties", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy).GetValue(StructReference, null).ToDictionary <object, object>().Keys.Any(x => (ChaListDefine.CategoryNo)x.GetField("Category") == categoryNo) || StructReference.GetProperty("ChaFileHairProperties", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy).GetValue(StructReference, null).ToDictionary <object, object>().Keys.Any(x => (ChaListDefine.CategoryNo)x.GetField("Category") == categoryNo) || StructReference.GetProperty("ChaFileMakeupProperties", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy).GetValue(StructReference, null).ToDictionary <object, object>().Keys.Any(x => (ChaListDefine.CategoryNo)x.GetField("Category") == categoryNo)); } //extInfo整理 int cleanExtData(ref PluginData tmpExtData, bool keepBodyData) { tmpExtData = ExtendedSave.GetExtendedDataById(ocichar.charInfo.chaFile, ext); if (tmpExtData != null && tmpExtData.data.ContainsKey("info")) { if (tmpExtData.data.TryGetValue("info", out object tmpExtInfo)) { if (null != tmpExtInfo as object[]) { List <object> tmpExtList = new List <object>(tmpExtInfo as object[]); Logger.LogDebug($"Sideloader count: {tmpExtList.Count}"); ResolveInfo tmpResolveInfo; for (int j = 0; j < tmpExtList.Count;) { tmpResolveInfo = (ResolveInfo)Extension.Extension.InvokeStatic(typeof(ResolveInfo), "Deserialize", new object[] { (byte[])tmpExtList[j] }); if (keepBodyData == isBelongsToCharaBody(tmpResolveInfo.CategoryNo)) { Logger.LogDebug($"Add Sideloader info: {tmpResolveInfo.GUID} : {tmpResolveInfo.Property} : {tmpResolveInfo.Slot}"); j++; } else { Logger.LogDebug($"Remove Sideloader info: {tmpResolveInfo.GUID} : {tmpResolveInfo.Property} : {tmpResolveInfo.Slot}"); tmpExtList.RemoveAt(j); } } tmpExtData.data["info"] = tmpExtList.ToArray(); return(tmpExtList.Count); } } } return(0); } //提出角色身上原始的Sideloader extData PluginData oldExtData = ExtendedSave.GetExtendedDataById(ocichar.charInfo.chaFile, ext); Logger.LogDebug($"Get Old Sideloader Start"); int L1 = cleanExtData(ref oldExtData, false); Logger.LogDebug($"Get Old Sideloader: {L1}"); //將擴充資料由暫存複製到角色身上 ExtendedSave.SetExtendedDataById(ocichar.charInfo.chaFile, ext, ExtendedSave.GetExtendedDataById(tmpChaFile, ext)); //清理新角色數據 PluginData newExtData = ExtendedSave.GetExtendedDataById(ocichar.charInfo.chaFile, ext); Logger.LogDebug($"Get New Sideloader Start"); int L2 = cleanExtData(ref newExtData, true); Logger.LogDebug($"Get New Sideloader: {L2}"); //合併新舊數據 object[] tmpObj = new object[L1 + L2]; (oldExtData?.data?["info"] as object[])?.CopyTo(tmpObj, 0); (newExtData?.data?["info"] as object[])?.CopyTo(tmpObj, L1); PluginData extData = null; if (tmpObj.Length != 0) { extData = new PluginData { data = new Dictionary <string, object> { ["info"] = tmpObj } }; } //儲存 ExtendedSave.SetExtendedDataById(ocichar.charInfo.chaFile, ext, extData); Logger.LogDebug($"Merge and Save Sideloader: {tmpObj.Length}"); //調用原始sideloader載入hook function Extension.Extension.InvokeStatic(typeof(UniversalAutoResolver).GetNestedType("Hooks", BindingFlags.NonPublic), "ExtendedCardLoad", new object[] { ocichar.charInfo.chaFile }); break; #endregion default: ExtendedSave.SetExtendedDataById(ocichar.charInfo.chaFile, ext, ExtendedSave.GetExtendedDataById(tmpChaFile, ext)); break; } Logger.LogDebug($"Change Extended Data: {ext}"); } return(true); }