/// <summary> /// Searches the recipe of the current active race for all its slots and builds a list of available bones from the slots meshData /// </summary> public void UpdateSeverablesFromActiveRecipe(string[] filters = null) { Debug.Log("UpdateSeverablesFromActiveRecipe for " + _characterAvatar.activeRace.name); var umaSeverablesList = new List <UMATransform>(); var selectedUmaSeverablesList = new List <UMATransform>(); UMAData.UMARecipe umaRecipe = new UMAData.UMARecipe(); _characterAvatar.activeRace.data.baseRaceRecipe.Load(umaRecipe, _characterAvatar.context); foreach (SlotData slot in umaRecipe.GetAllSlots()) { if (slot == null || slot.asset == null || slot.asset.meshData == null || slot.asset.meshData.umaBones == null) { continue; } //UMATransform doesn't have a System.IEquatable<UMATransform> so we have to compare by hash foreach (UMATransform umaTrans in slot.asset.meshData.umaBones) { //Find out if umaSeverablesList already has this UMATransform in it bool canAdd = !IsUMATransformInList(umaTrans, umaSeverablesList); if (canAdd) { //filter out any bones we dont want if (filters != null) { for (int fi = 0; fi < filters.Length; fi++) { if (!string.IsNullOrEmpty(filters[fi]) && umaTrans.name.IndexOf(filters[fi]) > -1) { canAdd = false; } } } if (canAdd) { umaSeverablesList.Add(umaTrans); } } } } if (_cachedRaceSeverables.ContainsKey(_characterAvatar.activeRace.name)) { //used the cached UMATransforms if we have them selectedUmaSeverablesList = new List <UMATransform>(_cachedRaceSeverables[_characterAvatar.activeRace.name]); } else { //only add any selected UMATrans that are in the new list for (int i = 0; i < _selectedUmaSeverables.Length; i++) { if (IsUMATransformInList(_selectedUmaSeverables[i], umaSeverablesList)) { selectedUmaSeverablesList.Add(_selectedUmaSeverables[i]); } } } //can we sort _umaSeverables so it reflects the bone heirarcy? Its pretty horrible like this. Probably can since each bone contains 'parent' data _umaSeverables = SortUMASeverablesList(umaSeverablesList).ToArray(); _selectedUmaSeverables = selectedUmaSeverablesList.ToArray(); }
//generate an option list for the BaseSlots that are available to hide for each race so we can make this a mask field too private void GenerateBaseSlotsEnum(List <string> compatibleRaces, bool forceUpdate = false) { if (generatedBaseSlotOptions.Count == 0 || forceUpdate) { //clear the lists if we are forcing update if (forceUpdate) { generatedBaseSlotOptions = new List <string>(); generatedBaseSlotOptionsLabels = new List <string>(); } List <UMARecipeBase> thisBaseRecipes = new List <UMARecipeBase>(); Dictionary <string, List <string> > slotsRacesDict = new Dictionary <string, List <string> >(); for (int i = 0; i < compatibleRaces.Count; i++) { if (GetCompatibleRaceData(compatibleRaces[i]) == null) { continue; } thisBaseRecipes.Add(GetCompatibleRaceData(compatibleRaces[i]).baseRaceRecipe); } for (int i = 0; i < thisBaseRecipes.Count; i++) { if (thisBaseRecipes[i] != null) { UMAData.UMARecipe thisBaseRecipe = thisBaseRecipes[i].GetCachedRecipe(UMAContext.Instance); SlotData[] thisSlots = thisBaseRecipe.GetAllSlots(); foreach (SlotData slot in thisSlots) { if (slot != null) { if (!generatedBaseSlotOptions.Contains(slot.asset.slotName)) { generatedBaseSlotOptions.Add(slot.asset.slotName); } if (!slotsRacesDict.ContainsKey(slot.asset.slotName)) { slotsRacesDict.Add(slot.asset.slotName, new List <string>()); } slotsRacesDict[slot.asset.slotName].Add(compatibleRaces[i]); } } } } //sort out the labels showing which race(s) the base slots are for if there is more than one compatible race foreach (KeyValuePair <string, List <string> > kp in slotsRacesDict) { string compatibleRaceNames = ""; if (compatibleRaces.Count > 1) { compatibleRaceNames = " (" + String.Join(", ", kp.Value.ToArray()) + ")"; } generatedBaseSlotOptionsLabels.Add(kp.Key + compatibleRaceNames); } } }
private void DrawCCUI(string ccRaceName, SerializedProperty baseRaceRecipe, SerializedProperty thisCCSettings) { GUIHelper.BeginVerticalPadded(5, new Color(0.75f, 0.875f, 1f)); EditorGUILayout.LabelField("Equivalent Slots with " + ccRaceName, EditorStyles.centeredGreyMiniLabel); if (baseRaceRecipe.objectReferenceValue == null) { EditorGUILayout.HelpBox("Please set this Races 'Base Race Recipe' before trying to set equivalent Slots.", MessageType.Warning); } else { //we need to get the base raceRecipeSlots for this compatible race var ccRaceData = GetCompatibleRaceData(ccRaceName); if (ccRaceData != null) { if (ccRaceData.baseRaceRecipe == null) { EditorGUILayout.HelpBox("Please set " + ccRaceData.raceName + " Races 'Base Race Recipe' before trying to set equivalent Slots.", MessageType.Warning); } else { var ccSlotsList = new List <SlotData>(); var ccSlotsNamesList = new List <string>(); UMAData.UMARecipe ccBaseRecipe = ccRaceData.baseRaceRecipe.GetCachedRecipe(UMAContextBase.Instance); SlotData[] ccBaseSlots = ccBaseRecipe.GetAllSlots(); foreach (SlotData slot in ccBaseSlots) { if (slot != null) { ccSlotsList.Add(slot); ccSlotsNamesList.Add(slot.slotName); } } //if that worked we can draw the UI for any set values and a button to add new ones GUIHelper.BeginVerticalPadded(2, new Color(1f, 1f, 1f, 0.5f)); var headerRect = GUILayoutUtility.GetRect(0.0f, (EditorGUIUtility.singleLineHeight * 2), GUILayout.ExpandWidth(true)); var slotLabelRect = headerRect; var gapRect = headerRect; var cSlotLabelRect = headerRect; var overlaysMatchLabelRect = headerRect; var deleteRect = headerRect; slotLabelRect.width = (headerRect.width - 50f - 22f - 22f) / 2; gapRect.xMin = slotLabelRect.xMax; gapRect.width = 22f; cSlotLabelRect.xMin = gapRect.xMax; cSlotLabelRect.width = slotLabelRect.width; overlaysMatchLabelRect.xMin = cSlotLabelRect.xMax; overlaysMatchLabelRect.width = 50f; deleteRect.xMin = overlaysMatchLabelRect.xMax; deleteRect.width = 22f; //move this up var tableHeaderStyle = EditorStyles.wordWrappedMiniLabel; tableHeaderStyle.alignment = TextAnchor.MiddleCenter; //we need a gui style for this that wraps the text and vertically centers it in the space EditorGUI.LabelField(slotLabelRect, "This Races Slot", tableHeaderStyle); EditorGUI.LabelField(gapRect, "", tableHeaderStyle); EditorGUI.LabelField(cSlotLabelRect, "Compatible Races Slot", tableHeaderStyle); EditorGUI.LabelField(overlaysMatchLabelRect, "Overlays Match", tableHeaderStyle); GUIHelper.EndVerticalPadded(2); GUIHelper.BeginVerticalPadded(2, new Color(0.75f, 0.875f, 1f)); if (thisCCSettings.arraySize > 0) { for (int ccsd = 0; ccsd < thisCCSettings.arraySize; ccsd++) { if (DrawCCUISetting(ccsd, thisCCSettings, ccSlotsNamesList)) { serializedObject.ApplyModifiedProperties(); } } } else { EditorGUILayout.LabelField("No equivalent slots defined", EditorStyles.miniLabel); } GUIHelper.EndVerticalPadded(2); var addButtonRect = GUILayoutUtility.GetRect(0.0f, EditorGUIUtility.singleLineHeight, GUILayout.ExpandWidth(true)); addButtonRect.xMin = addButtonRect.xMax - 70f; addButtonRect.width = 70f; if (GUI.Button(addButtonRect, "Add")) { thisCCSettings.InsertArrayElementAtIndex(thisCCSettings.arraySize); serializedObject.ApplyModifiedProperties(); } } } else { EditorGUILayout.HelpBox("The cross compatible race " + ccRaceName + " could not be found!", MessageType.Warning); } } GUIHelper.EndVerticalPadded(5); }
public bool AddExtraStuff() { SerializedProperty baseRaceRecipe = serializedObject.FindProperty("baseRaceRecipe"); EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(baseRaceRecipe, true); if (EditorGUI.EndChangeCheck()) { serializedObject.ApplyModifiedProperties(); } if (wardrobeSlotList == null) { InitWardrobeSlotList(); } EditorGUILayout.Space(); EditorGUI.BeginChangeCheck(); wardrobeSlotList.DoLayoutList(); if (EditorGUI.EndChangeCheck()) { serializedObject.ApplyModifiedProperties(); if (!race.ValidateWardrobeSlots()) { EditorUtility.SetDirty(race); } } //new CrossCompatibilitySettings //To push any old settings in RaceData.backwardsCompatibleWith into the new crossCompatibilitySettings we have to call GetCrossCompatibleRaces() directly on the target #pragma warning disable 618 if (race.backwardsCompatibleWith.Count > 0) { var cc = race.GetCrossCompatibleRaces(); if (cc.Count > 0) { serializedObject.Update(); } } #pragma warning restore 618 SerializedProperty _crossCompatibilitySettings = serializedObject.FindProperty("_crossCompatibilitySettings"); SerializedProperty _crossCompatibilitySettingsData = _crossCompatibilitySettings.FindPropertyRelative("settingsData"); //draw the new version of the crossCompatibility list that allows users to define what slots in this races base recipe equate to in the backwards compatible races base recipe _crossCompatibilitySettings.isExpanded = EditorGUILayout.Foldout(_crossCompatibilitySettings.isExpanded, "Cross Compatibility Settings"); if (_crossCompatibilitySettings.isExpanded) { //draw an info foldout EditorGUI.indentLevel++; _crossCompatibilitySettingsData.isExpanded = EditorGUILayout.Foldout(_crossCompatibilitySettingsData.isExpanded, "Help"); if (_crossCompatibilitySettingsData.isExpanded) { var helpText = "CrossCompatibilitySettings allows this race to wear wardrobe slots from another race, if this race has a wardrobe slot that the recipe is set to."; helpText += " You can further configure the compatibility settings for each compatible race to define 'equivalent' slotdatas in the races' base recipes."; helpText += " For example you could define that this races 'highpolyMaleChest' slotdata in its base recipe is equivalent to HumanMales 'MaleChest' slot data in its base recipe."; helpText += " This would mean that any recipes which hid or applied an overlay to 'MaleChest' would hide or apply an overlay to 'highPolyMaleChest' on this race."; helpText += " If 'Overlays Match' is unchecked then overlays in a recipe wont be applied."; EditorGUILayout.HelpBox(helpText, MessageType.Info); } EditorGUI.indentLevel--; if (baseRaceRecipe.objectReferenceValue != null) { Rect dropArea = new Rect(); dropArea = GUILayoutUtility.GetRect(0.0f, 50.0f, GUILayout.ExpandWidth(true)); GUI.Box(dropArea, "Drag cross compatible Races here. Click to pick."); CompatibleRacesDropArea(dropArea, _crossCompatibilitySettingsData); EditorGUILayout.Space(); //update the foldouts list if the dropbox changes anything if (_BCFoldouts.Length != _crossCompatibilitySettingsData.arraySize) { Array.Resize <bool>(ref _BCFoldouts, _crossCompatibilitySettingsData.arraySize); } //we need an uptodate list of the slots in THIS races base recipe baseSlotsList.Clear(); baseSlotsNamesList.Clear(); //editing a race will require a context too because we need to get the base recipes and their slots if (UMAContextBase.Instance == null) { EditorUMAContextBase = UMAContextBase.CreateEditorContext(); } UMAData.UMARecipe thisBaseRecipe = (baseRaceRecipe.objectReferenceValue as UMARecipeBase).GetCachedRecipe(UMAContextBase.Instance); SlotData[] thisBaseSlots = thisBaseRecipe.GetAllSlots(); foreach (SlotData slot in thisBaseSlots) { if (slot != null) { baseSlotsList.Add(slot); baseSlotsNamesList.Add(slot.slotName); } } List <int> crossCompatibleSettingsToDelete = new List <int>(); //draw a foldout area for each compatible race that will show an entry for each slot in this races base recipe //with a picker to choose the slot from the compatible race's base recipe that it equates to for (int i = 0; i < _crossCompatibilitySettingsData.arraySize; i++) { bool del = false; var thisCCSettings = _crossCompatibilitySettingsData.GetArrayElementAtIndex(i).FindPropertyRelative("ccSettings"); var ccRaceName = _crossCompatibilitySettingsData.GetArrayElementAtIndex(i).FindPropertyRelative("ccRace").stringValue; //this could be missing- we should show that var label = ccRaceName; if (GetCompatibleRaceData(ccRaceName) == null) { label += " (missing)"; } GUIHelper.FoldoutBar(ref _BCFoldouts[i], label, out del); if (del) { crossCompatibleSettingsToDelete.Add(i); } if (_BCFoldouts[i]) { DrawCCUI(ccRaceName, baseRaceRecipe, thisCCSettings); } } if (crossCompatibleSettingsToDelete.Count > 0) { foreach (int del in crossCompatibleSettingsToDelete) { _crossCompatibilitySettingsData.DeleteArrayElementAtIndex(del); serializedObject.ApplyModifiedProperties(); } } } else { EditorGUILayout.HelpBox("Please define this races baseRaceRecipe before trying to define its cross compatibility settings.", MessageType.Info); } } EditorGUILayout.Space(); EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(serializedObject.FindProperty("raceThumbnails"), true); if (EditorGUI.EndChangeCheck()) { serializedObject.ApplyModifiedProperties(); } return(false); }