From 9ea23996ea8102dbc405f4bbb5c5f8163f37d9ec Mon Sep 17 00:00:00 2001 From: RhenaudTheLukark Date: Fri, 2 Aug 2024 04:07:09 +0200 Subject: [PATCH] [Modpack] Nested mod folder detection --- Assets/Scenes/ModSelect.unity | 316 ++++++++++++++++++ .../PregamePlaceholder/SelectOMatic.cs | 101 ++++-- Assets/Scripts/Util/UnitaleUtil.cs | 32 +- ProjectSettings/GraphicsSettings.asset | 1 + 4 files changed, 428 insertions(+), 22 deletions(-) diff --git a/Assets/Scenes/ModSelect.unity b/Assets/Scenes/ModSelect.unity index bb6534c54..6b9092142 100644 --- a/Assets/Scenes/ModSelect.unity +++ b/Assets/Scenes/ModSelect.unity @@ -1895,6 +1895,83 @@ CanvasRenderer: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 763720700} m_CullTransparentMesh: 0 +--- !u!1 &787902698 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 787902699} + - component: {fileID: 787902701} + - component: {fileID: 787902700} + m_Layer: 5 + m_Name: NestedFolderNameShadow + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &787902699 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 787902698} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 1369608972} + m_RootOrder: 5 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 1} + m_AnchorMax: {x: 0.5, y: 1} + m_AnchoredPosition: {x: 2, y: 118} + m_SizeDelta: {x: 640, y: 30} + m_Pivot: {x: 0.5, y: 1} +--- !u!114 &787902700 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 787902698} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 708705254, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0, g: 0, b: 0, a: 1} + m_RaycastTarget: 0 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 12800000, guid: a5a3b9aa4e92cfd4a9014c429c6a7e4e, type: 3} + m_FontSize: 16 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 0 + m_MaxSize: 40 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 0 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: Belongs to the folder Root +--- !u!222 &787902701 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 787902698} + m_CullTransparentMesh: 0 --- !u!1 &827182968 GameObject: m_ObjectHideFlags: 0 @@ -2164,6 +2241,83 @@ CanvasRenderer: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 911703477} m_CullTransparentMesh: 0 +--- !u!1 &965220556 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 965220557} + - component: {fileID: 965220559} + - component: {fileID: 965220558} + m_Layer: 5 + m_Name: ANIM NestedFolderNameShadow + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &965220557 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 965220556} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 2100541824} + m_RootOrder: 5 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 1} + m_AnchorMax: {x: 0.5, y: 1} + m_AnchoredPosition: {x: 2, y: 118} + m_SizeDelta: {x: 640, y: 30} + m_Pivot: {x: 0.5, y: 1} +--- !u!114 &965220558 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 965220556} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 708705254, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0, g: 0, b: 0, a: 1} + m_RaycastTarget: 0 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 12800000, guid: a5a3b9aa4e92cfd4a9014c429c6a7e4e, type: 3} + m_FontSize: 16 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 0 + m_MaxSize: 40 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 0 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: Belongs to the folder Root +--- !u!222 &965220559 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 965220556} + m_CullTransparentMesh: 0 --- !u!224 &1058685157 stripped RectTransform: m_CorrespondingSourceObject: {fileID: 6408004043003039569, guid: 06a74376a3db7d744b36f7bb52973cf7, @@ -2356,9 +2510,88 @@ Transform: - {fileID: 731984165} - {fileID: 533019934} - {fileID: 1490416946} + - {fileID: 787902699} + - {fileID: 1398906137} m_Father: {fileID: 64571900} m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1398906136 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1398906137} + - component: {fileID: 1398906139} + - component: {fileID: 1398906138} + m_Layer: 5 + m_Name: NestedFolderName + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1398906137 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1398906136} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 1369608972} + m_RootOrder: 6 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 1} + m_AnchorMax: {x: 0.5, y: 1} + m_AnchoredPosition: {x: 0, y: 120} + m_SizeDelta: {x: 640, y: 30} + m_Pivot: {x: 0.5, y: 1} +--- !u!114 &1398906138 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1398906136} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 708705254, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.5019608, g: 0.5019608, b: 0.5019608, a: 1} + m_RaycastTarget: 0 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 12800000, guid: a5a3b9aa4e92cfd4a9014c429c6a7e4e, type: 3} + m_FontSize: 16 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 0 + m_MaxSize: 40 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 0 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: Belongs to the folder Root +--- !u!222 &1398906139 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1398906136} + m_CullTransparentMesh: 0 --- !u!1 &1419761035 GameObject: m_ObjectHideFlags: 0 @@ -2436,6 +2669,83 @@ CanvasRenderer: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1419761035} m_CullTransparentMesh: 0 +--- !u!1 &1424652198 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1424652199} + - component: {fileID: 1424652201} + - component: {fileID: 1424652200} + m_Layer: 5 + m_Name: ANIM NestedFolderName + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1424652199 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1424652198} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 2100541824} + m_RootOrder: 6 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 1} + m_AnchorMax: {x: 0.5, y: 1} + m_AnchoredPosition: {x: 0, y: 120} + m_SizeDelta: {x: 640, y: 30} + m_Pivot: {x: 0.5, y: 1} +--- !u!114 &1424652200 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1424652198} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 708705254, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.5019608, g: 0.5019608, b: 0.5019608, a: 1} + m_RaycastTarget: 0 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 12800000, guid: a5a3b9aa4e92cfd4a9014c429c6a7e4e, type: 3} + m_FontSize: 16 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 0 + m_MaxSize: 40 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 0 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: Belongs to the folder Root +--- !u!222 &1424652201 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1424652198} + m_CullTransparentMesh: 0 --- !u!1 &1445560356 GameObject: m_ObjectHideFlags: 0 @@ -3316,12 +3626,16 @@ MonoBehaviour: ModTitleShadow: {fileID: 877904474} EncounterCount: {fileID: 1490416945} EncounterCountShadow: {fileID: 533019933} + FolderText: {fileID: 1398906136} + FolderTextShadow: {fileID: 787902698} AnimContainer: {fileID: 2100541823} AnimModBackground: {fileID: 677606676} AnimModTitle: {fileID: 1419761035} AnimModTitleShadow: {fileID: 530423193} AnimEncounterCount: {fileID: 1298088668} AnimEncounterCountShadow: {fileID: 1906607163} + AnimFolderText: {fileID: 1424652198} + AnimFolderTextShadow: {fileID: 965220556} --- !u!1 &2100541823 GameObject: m_ObjectHideFlags: 0 @@ -3354,6 +3668,8 @@ Transform: - {fileID: 1419761036} - {fileID: 1906607164} - {fileID: 1298088669} + - {fileID: 965220557} + - {fileID: 1424652199} m_Father: {fileID: 64571900} m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} diff --git a/Assets/Scripts/PregamePlaceholder/SelectOMatic.cs b/Assets/Scripts/PregamePlaceholder/SelectOMatic.cs index 37831c258..c1509ea23 100644 --- a/Assets/Scripts/PregamePlaceholder/SelectOMatic.cs +++ b/Assets/Scripts/PregamePlaceholder/SelectOMatic.cs @@ -27,8 +27,8 @@ public class SelectOMatic : MonoBehaviour { public GameObject encounterBox, devMod, content, retromodeWarning; public GameObject btnList, btnBack, btnNext, btnExit, btnOptions; public Text ListText, ListShadow, BackText, BackShadow, NextText, NextShadow, ExitText, ExitShadow, OptionsText, OptionsShadow; - public GameObject ModContainer, ModBackground, ModTitle, ModTitleShadow, EncounterCount, EncounterCountShadow; - public GameObject AnimContainer, AnimModBackground, AnimModTitle, AnimModTitleShadow, AnimEncounterCount, AnimEncounterCountShadow; + public GameObject ModContainer, ModBackground, ModTitle, ModTitleShadow, EncounterCount, EncounterCountShadow, FolderText, FolderTextShadow; + public GameObject AnimContainer, AnimModBackground, AnimModTitle, AnimModTitleShadow, AnimEncounterCount, AnimEncounterCountShadow, AnimFolderText, AnimFolderTextShadow; // Use this for initialization private void Start() { @@ -39,17 +39,10 @@ private void Start() { UnitaleUtil.firstErrorShown = false; // Load directory info - DirectoryInfo di = new DirectoryInfo(Path.Combine(FileLoader.DataRoot, "Mods")); - var modDirsTemp = di.GetDirectories(); - - // Remove mods with 0 encounters and hidden mods from the list - List purged = (from modDir in modDirsTemp - let encPath = Path.Combine(FileLoader.DataRoot, "Mods/" + modDir.Name + "/Lua/Encounters") - where new DirectoryInfo(encPath).Exists - let hasEncounters = new DirectoryInfo(encPath).GetFiles("*.lua").Where(e => !e.Name.StartsWith("@")).Any() - where hasEncounters && (modDir.Attributes & FileAttributes.Hidden) != FileAttributes.Hidden && !modDir.Name.StartsWith("@") - select modDir).ToList(); - modDirs = purged; + DirectoryInfo modsFolder = new DirectoryInfo(Path.Combine(FileLoader.DataRoot, "Mods")); + + // Deep mod detection in CYF's Mods folder + modDirs = DeepModSearch(modsFolder); // Make sure that there is at least one playable mod present if (modDirs.Count == 0) { @@ -119,7 +112,8 @@ private void Start() { ListShadow.gameObject.GetComponent().text = "MDO LITS"; } - retromodeWarning.SetActive(GlobalControls.retroMode); + if (retromodeWarning) + retromodeWarning.SetActive(GlobalControls.retroMode); // This check will be true if we just exited out of an encounter // If that's the case, we want to open the encounter list so the user only has to click once to re enter @@ -162,6 +156,57 @@ private void Start() { } } + /// + /// This function performs a deep search for mods in the selected folder. + /// Note that the function is recursive: it calls itself on subfolders if it finds any. + /// A mod must satisfy a few conditions to be detected: + /// - It must contain the folders Sprites and Lua/Encounters. + /// - Its Lua/Encounters folder must contain at least one sprite. + /// - Its root folder must not be CYF's Mods folder. + /// - Its root folder must not be hidden nor start with the character @. + /// + /// + /// + /// + /// + private List DeepModSearch(DirectoryInfo dir, int currentDepth = 0, int maxDepth = 8) { + List mods = new List(); + foreach (DirectoryInfo encountersFolder in dir.GetDirectories()) { + // Recursive call + if (currentDepth < maxDepth && encountersFolder.GetDirectories().Length > 0) + mods.AddRange(DeepModSearch(encountersFolder, currentDepth + 1, maxDepth)); + + // The current folder should be named Encounters and must contain at least one file. + if (encountersFolder.Name != "Encounters" || encountersFolder.GetFiles().Length == 0) + continue; + + DirectoryInfo luaFolder = encountersFolder.Parent; + // The Encounters folder's parent should be named Lua. + if (luaFolder == null || luaFolder.Name != "Lua") + continue; + + DirectoryInfo modRootFolder = luaFolder.Parent; + // The root of the mod should not be the CYF Mods folder. + if (modRootFolder == null || modRootFolder.FullName == Path.Combine(FileLoader.DataRoot, "Mods")) + continue; + // The root of the mod should not start with the symbol @. + if (modRootFolder.Name.StartsWith("@")) + continue; + // The root of the mod should not be hidden. + if ((modRootFolder.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden) + continue; + + // The Lua folder should have a sibling folder named Sprites. + DirectoryInfo spritesFolder = modRootFolder.GetDirectories().SingleOrDefault(d => d.Name == "Sprites"); + if (spritesFolder == null) + continue; + + mods.Add(modRootFolder); + } + + return mods; + } + // A special function used specifically for error handling // It re-generates the mod list, and selects the first mod // Used for cases where the player selects a mod or encounter that no longer exists @@ -174,8 +219,8 @@ private void HandleErrors() { private IEnumerator LaunchMod() { // First: make sure the mod is still here and can be opened - if (!new DirectoryInfo(Path.Combine(FileLoader.DataRoot, "Mods/" + modDirs[CurrentSelectedMod].Name + "/Lua/Encounters/")).Exists - || !File.Exists(Path.Combine(FileLoader.DataRoot, "Mods/" + modDirs[CurrentSelectedMod].Name + "/Lua/Encounters/" + StaticInits.ENCOUNTER + ".lua"))) { + if (!new DirectoryInfo(modDirs[CurrentSelectedMod].FullName + "/Lua/Encounters/").Exists + || !File.Exists(modDirs[CurrentSelectedMod].FullName + "/Lua/Encounters/" + StaticInits.ENCOUNTER + ".lua")) { HandleErrors(); yield break; } @@ -206,14 +251,14 @@ private void ShowMod(int id) { // Error handler // If current index is now out of range OR currently selected mod is not present: if (id < 0 || id > modDirs.Count - 1 - || !new DirectoryInfo(Path.Combine(FileLoader.DataRoot, "Mods/" + modDirs[id].Name + "/Lua/Encounters")).Exists - || new DirectoryInfo(Path.Combine(FileLoader.DataRoot, "Mods/" + modDirs[id].Name + "/Lua/Encounters")).GetFiles("*.lua").Length == 0) { + || !new DirectoryInfo(modDirs[id].FullName + "/Lua/Encounters").Exists + || new DirectoryInfo(modDirs[id].FullName + "/Lua/Encounters").GetFiles("*.lua").Length == 0) { HandleErrors(); return; } // Update currently selected mod folder - StaticInits.MODFOLDER = modDirs[id].Name; + StaticInits.MODFOLDER = UnitaleUtil.MakeRelativePath(Path.Combine(FileLoader.DataRoot, "Mods/"), modDirs[id].FullName); // Make clicking the background go to the encounter select screen ModBackground.GetComponent