SceneManager.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. using System;
  2. using System.IO;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using UnityEngine;
  6. using BepInEx.Configuration;
  7. namespace COM3D2.MeidoPhotoStudio.Plugin
  8. {
  9. internal class SceneManager : IManager
  10. {
  11. public static bool Busy { get; private set; } = false;
  12. public bool Initialized { get; private set; } = false;
  13. private MeidoPhotoStudio meidoPhotoStudio;
  14. private SceneModalWindow sceneModal;
  15. private int SortDirection => SortDescending ? -1 : 1;
  16. public static Vector2 sceneDimensions = new Vector2(480, 270);
  17. public bool KankyoMode { get; set; } = false;
  18. private static readonly ConfigEntry<bool> sortDescending;
  19. public bool SortDescending
  20. {
  21. get => sortDescending.Value;
  22. set => sortDescending.Value = value;
  23. }
  24. public List<Scene> SceneList { get; private set; } = new List<Scene>();
  25. public int CurrentDirectoryIndex { get; private set; } = -1;
  26. public string CurrentDirectoryName => CurrentDirectoryList[CurrentDirectoryIndex];
  27. public List<string> CurrentDirectoryList
  28. {
  29. get => KankyoMode ? Constants.KankyoDirectoryList : Constants.SceneDirectoryList;
  30. }
  31. public string CurrentBasePath => KankyoMode ? Constants.kankyoPath : Constants.scenesPath;
  32. public string CurrentScenesDirectory
  33. {
  34. get => CurrentDirectoryIndex == 0 ? CurrentBasePath : Path.Combine(CurrentBasePath, CurrentDirectoryName);
  35. }
  36. private static readonly ConfigEntry<SortMode> currentSortMode;
  37. public SortMode CurrentSortMode
  38. {
  39. get => currentSortMode.Value;
  40. private set => currentSortMode.Value = value;
  41. }
  42. public int CurrentSceneIndex { get; private set; } = -1;
  43. public Scene CurrentScene
  44. {
  45. get
  46. {
  47. if (SceneList.Count == 0) return null;
  48. return SceneList[CurrentSceneIndex];
  49. }
  50. }
  51. public enum SortMode
  52. {
  53. Name, DateCreated, DateModified
  54. }
  55. static SceneManager()
  56. {
  57. sortDescending = Configuration.Config.Bind<bool>(
  58. "SceneManager", "SortDescending",
  59. false,
  60. "Sort scenes descending (Z-A)"
  61. );
  62. currentSortMode = Configuration.Config.Bind<SortMode>(
  63. "SceneManager", "SortMode",
  64. SortMode.Name,
  65. "Scene sorting mode"
  66. );
  67. }
  68. public SceneManager(MeidoPhotoStudio meidoPhotoStudio)
  69. {
  70. this.meidoPhotoStudio = meidoPhotoStudio;
  71. this.sceneModal = new SceneModalWindow(this);
  72. }
  73. public void Activate() { }
  74. public void Initialize()
  75. {
  76. if (!Initialized)
  77. {
  78. Initialized = true;
  79. SelectDirectory(0);
  80. }
  81. }
  82. public void Deactivate() => ClearSceneList();
  83. public void Update()
  84. {
  85. if (Utility.GetModKey(Utility.ModKey.Control))
  86. {
  87. if (Input.GetKeyDown(KeyCode.S)) QuickSaveScene();
  88. else if (Input.GetKeyDown(KeyCode.A)) QuickLoadScene();
  89. }
  90. }
  91. public void DeleteDirectory()
  92. {
  93. if (Directory.Exists(CurrentScenesDirectory))
  94. {
  95. Directory.Delete(CurrentScenesDirectory, true);
  96. }
  97. CurrentDirectoryList.RemoveAt(CurrentDirectoryIndex);
  98. CurrentDirectoryIndex = Mathf.Clamp(CurrentDirectoryIndex, 0, CurrentDirectoryList.Count - 1);
  99. UpdateSceneList();
  100. }
  101. public void OverwriteScene() => SaveScene(overwrite: true);
  102. public void ToggleKankyoMode()
  103. {
  104. this.KankyoMode = !this.KankyoMode;
  105. CurrentDirectoryIndex = 0;
  106. UpdateSceneList();
  107. }
  108. public void SaveScene(bool overwrite = false)
  109. {
  110. if (Busy) return;
  111. if (!Directory.Exists(CurrentScenesDirectory)) Directory.CreateDirectory(CurrentScenesDirectory);
  112. meidoPhotoStudio.StartCoroutine(SaveSceneToFile(overwrite));
  113. }
  114. public void SelectDirectory(int directoryIndex)
  115. {
  116. directoryIndex = Mathf.Clamp(directoryIndex, 0, CurrentDirectoryList.Count - 1);
  117. if (directoryIndex == CurrentDirectoryIndex) return;
  118. CurrentDirectoryIndex = directoryIndex;
  119. UpdateSceneList();
  120. }
  121. public void SelectScene(int sceneIndex)
  122. {
  123. CurrentSceneIndex = Mathf.Clamp(sceneIndex, 0, SceneList.Count - 1);
  124. CurrentScene.GetNumberOfMaids();
  125. }
  126. public void AddDirectory(string directoryName)
  127. {
  128. directoryName = Utility.SanitizePathPortion(directoryName);
  129. if (!CurrentDirectoryList.Contains(directoryName, StringComparer.InvariantCultureIgnoreCase))
  130. {
  131. string finalPath = Path.Combine(CurrentBasePath, directoryName);
  132. string fullPath = Path.GetFullPath(finalPath);
  133. if (!fullPath.StartsWith(CurrentBasePath))
  134. {
  135. string baseDirectoryName = KankyoMode ? Constants.kankyoDirectory : Constants.sceneDirectory;
  136. Utility.LogError($"Could not add directory to {baseDirectoryName}. Path is invalid: '{fullPath}'");
  137. return;
  138. }
  139. CurrentDirectoryList.Add(directoryName);
  140. Directory.CreateDirectory(finalPath);
  141. UpdateDirectoryList();
  142. CurrentDirectoryIndex = CurrentDirectoryList.IndexOf(directoryName);
  143. UpdateSceneList();
  144. }
  145. }
  146. public void Refresh()
  147. {
  148. if (!Directory.Exists(CurrentScenesDirectory)) CurrentDirectoryIndex = 0;
  149. Constants.InitializeScenes();
  150. UpdateSceneList();
  151. }
  152. public void SortScenes(SortMode sortMode)
  153. {
  154. CurrentSortMode = sortMode;
  155. Comparison<Scene> comparator;
  156. switch (CurrentSortMode)
  157. {
  158. case SortMode.DateModified: comparator = SortByDateModified; break;
  159. case SortMode.DateCreated: comparator = SortByDateCreated; break;
  160. default: comparator = SortByName; break;
  161. }
  162. SceneList.Sort(comparator);
  163. }
  164. public void DeleteScene()
  165. {
  166. if (CurrentScene.FileInfo.Exists)
  167. {
  168. CurrentScene.FileInfo.Delete();
  169. }
  170. SceneList.RemoveAt(CurrentSceneIndex);
  171. CurrentSceneIndex = Mathf.Clamp(CurrentSceneIndex, 0, SceneList.Count - 1);
  172. }
  173. public void LoadScene()
  174. {
  175. meidoPhotoStudio.ApplyScene(CurrentScene.FileInfo.FullName);
  176. }
  177. private int SortByName(Scene a, Scene b)
  178. {
  179. return SortDirection * LexicographicStringComparer.Comparison(a.FileInfo.Name, b.FileInfo.Name);
  180. }
  181. private int SortByDateCreated(Scene a, Scene b)
  182. {
  183. return SortDirection * DateTime.Compare(a.FileInfo.CreationTime, b.FileInfo.CreationTime);
  184. }
  185. private int SortByDateModified(Scene a, Scene b)
  186. {
  187. return SortDirection * DateTime.Compare(a.FileInfo.LastWriteTime, b.FileInfo.LastWriteTime);
  188. }
  189. private void UpdateSceneList()
  190. {
  191. ClearSceneList();
  192. if (!Directory.Exists(CurrentScenesDirectory))
  193. {
  194. Directory.CreateDirectory(CurrentScenesDirectory);
  195. }
  196. foreach (string filename in Directory.GetFiles(CurrentScenesDirectory))
  197. {
  198. if (Path.GetExtension(filename) == ".png") SceneList.Add(new Scene(filename));
  199. }
  200. SortScenes(CurrentSortMode);
  201. CurrentSceneIndex = Mathf.Clamp(CurrentSceneIndex, 0, SceneList.Count - 1);
  202. }
  203. private void UpdateDirectoryList()
  204. {
  205. string baseDirectoryName = KankyoMode ? Constants.kankyoDirectory : Constants.sceneDirectory;
  206. CurrentDirectoryList.Sort((a, b) =>
  207. {
  208. if (a.Equals(baseDirectoryName, StringComparison.InvariantCultureIgnoreCase)) return -1;
  209. else return a.CompareTo(b);
  210. });
  211. }
  212. private void ClearSceneList()
  213. {
  214. foreach (Scene scene in SceneList) scene.Destroy();
  215. SceneList.Clear();
  216. }
  217. private void QuickSaveScene()
  218. {
  219. if (Busy) return;
  220. byte[] data = meidoPhotoStudio.SerializeScene(kankyo: false);
  221. if (data == null) return;
  222. File.WriteAllBytes(Path.Combine(Constants.configPath, "mpstempscene"), data);
  223. }
  224. private void QuickLoadScene()
  225. {
  226. if (Busy) return;
  227. meidoPhotoStudio.ApplyScene(Path.Combine(Constants.configPath, "mpstempscene"));
  228. }
  229. private System.Collections.IEnumerator SaveSceneToFile(bool overwrite = false)
  230. {
  231. Busy = true;
  232. byte[] sceneData = meidoPhotoStudio.SerializeScene(KankyoMode);
  233. if (sceneData != null)
  234. {
  235. string screenshotPath = Utility.TempScreenshotFilename();
  236. MeidoPhotoStudio.TakeScreenshot(screenshotPath, 1, KankyoMode);
  237. do yield return new WaitForSecondsRealtime(0.2f);
  238. while (!File.Exists(screenshotPath));
  239. string scenePrefix = KankyoMode ? "mpskankyo" : "mpsscene";
  240. string fileName = $"{scenePrefix}{System.DateTime.Now:yyyyMMddHHmmss}.png";
  241. string savePath = Path.Combine(CurrentScenesDirectory, fileName);
  242. if (overwrite && CurrentScene != null)
  243. {
  244. savePath = CurrentScene.FileInfo.FullName;
  245. }
  246. else overwrite = false;
  247. Texture2D screenshot = new Texture2D(1, 1, TextureFormat.ARGB32, false);
  248. screenshot.LoadImage(File.ReadAllBytes(screenshotPath));
  249. int sceneWidth = (int)SceneManager.sceneDimensions.x;
  250. int sceneHeight = (int)SceneManager.sceneDimensions.y;
  251. Utility.ResizeToFit(screenshot, sceneWidth, sceneHeight);
  252. using (FileStream fileStream = File.Create(savePath))
  253. {
  254. byte[] encodedPng = screenshot.EncodeToPNG();
  255. fileStream.Write(encodedPng, 0, encodedPng.Length);
  256. fileStream.Write(sceneData, 0, sceneData.Length);
  257. }
  258. UnityEngine.Object.DestroyImmediate(screenshot);
  259. if (overwrite)
  260. {
  261. File.SetCreationTime(savePath, CurrentScene.FileInfo.CreationTime);
  262. CurrentScene.Destroy();
  263. SceneList.RemoveAt(CurrentSceneIndex);
  264. }
  265. SceneList.Add(new Scene(savePath));
  266. SortScenes(CurrentSortMode);
  267. }
  268. Busy = false;
  269. }
  270. public class Scene
  271. {
  272. public const int initialNumberOfMaids = -2;
  273. public Texture2D Thumbnail { get; private set; }
  274. public FileInfo FileInfo { get; set; }
  275. public int NumberOfMaids { get; private set; } = initialNumberOfMaids;
  276. public Scene(string filePath)
  277. {
  278. FileInfo = new FileInfo(filePath);
  279. Thumbnail = new Texture2D(1, 1, TextureFormat.ARGB32, false);
  280. Thumbnail.LoadImage(File.ReadAllBytes(FileInfo.FullName));
  281. }
  282. public void GetNumberOfMaids()
  283. {
  284. if (NumberOfMaids != initialNumberOfMaids) return;
  285. string filePath = FileInfo.FullName;
  286. byte[] sceneData = MeidoPhotoStudio.DecompressScene(filePath);
  287. if (sceneData == null) return;
  288. using (MemoryStream memoryStream = new MemoryStream(sceneData))
  289. using (BinaryReader binaryReader = new BinaryReader(memoryStream, System.Text.Encoding.UTF8))
  290. {
  291. try
  292. {
  293. if (binaryReader.ReadString() != "MPS_SCENE")
  294. {
  295. Utility.LogWarning($"'{filePath}' is not a {MeidoPhotoStudio.pluginName} scene");
  296. return;
  297. }
  298. if (binaryReader.ReadInt32() > MeidoPhotoStudio.sceneVersion)
  299. {
  300. Utility.LogWarning(
  301. $"'{filePath}' is made in a newer version of {MeidoPhotoStudio.pluginName}"
  302. );
  303. return;
  304. }
  305. NumberOfMaids = binaryReader.ReadInt32();
  306. }
  307. catch (Exception e)
  308. {
  309. Utility.LogWarning($"Failed to deserialize scene '{filePath}' because {e.Message}");
  310. return;
  311. }
  312. }
  313. }
  314. public void Destroy()
  315. {
  316. if (Thumbnail != null) UnityEngine.Object.DestroyImmediate(Thumbnail);
  317. }
  318. }
  319. }
  320. }